├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include ├── tssx │ ├── bridge.h │ ├── buffer.h │ ├── client-overrides.h │ ├── common-overrides.h │ ├── common-poll-overrides.h │ ├── connection-options.h │ ├── connection.h │ ├── definitions.h │ ├── epoll-overrides.h │ ├── free-list.h │ ├── hashtable.h │ ├── poll-overrides.h │ ├── reverse-map.h │ ├── select-overrides.h │ ├── selective.h │ ├── server-overrides.h │ ├── session-table.h │ ├── session.h │ ├── shared-memory.h │ ├── socket-overrides.h │ ├── string-set.h │ ├── timeouts.h │ └── vector.h └── utility │ ├── arguments.h │ ├── benchmarks.h │ ├── common.h │ ├── parent.h │ ├── process.h │ ├── signals.h │ ├── sockets.h │ └── utility.h ├── other └── notes.md ├── scripts ├── run-client.sh ├── run-server.sh ├── run-try-client.sh └── run-try-server.sh ├── source ├── CMakeLists.txt ├── experiment │ ├── CMakeLists.txt │ ├── client.c │ ├── run-client.sh │ ├── run-server.sh │ └── server.c ├── tssx │ ├── CMakeLists.txt │ ├── bridge.c │ ├── buffer.c │ ├── client-overrides.c │ ├── common-overrides.c │ ├── common-poll-overrides.c │ ├── connection-options.c │ ├── connection.c │ ├── epoll-overrides.c │ ├── free-list.c │ ├── hashtable.c │ ├── poll-overrides.c │ ├── reverse-map.c │ ├── select-overrides.c │ ├── selective.c │ ├── server-overrides.c │ ├── session-table.c │ ├── session.c │ ├── shared-memory.c │ ├── socket-overrides.c │ ├── string-set.c │ ├── timeouts.c │ └── try.c └── utility │ ├── CMakeLists.txt │ ├── arguments.c │ ├── benchmarks.c │ ├── parent.c │ ├── process.c │ ├── signals.c │ ├── sockets.c │ └── utility.c └── try ├── CMakeLists.txt ├── client ├── server ├── try-client.c ├── try-common.c ├── try-common.h ├── try-epoll.c ├── try-epoll.h ├── try-poll.c ├── try-poll.h ├── try-select.c ├── try-select.h └── try-server.c /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | Standard: Cpp11 3 | BasedOnStyle: Google 4 | 5 | AllowAllParametersOfDeclarationOnNextLine: true 6 | AllowShortBlocksOnASingleLine: false 7 | AllowShortCaseLabelsOnASingleLine: true 8 | AllowShortFunctionsOnASingleLine: false 9 | AllowShortIfStatementsOnASingleLine: true 10 | AllowShortLoopsOnASingleLine: true 11 | 12 | BinPackArguments: false 13 | BinPackParameters: false 14 | BreakConstructorInitializersBeforeComma: true 15 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 16 | ConstructorInitializerIndentWidth: 0 17 | ContinuationIndentWidth: 4 18 | Cpp11BracedListStyle: true 19 | IndentCaseLabels: true 20 | IndentWidth: 2 21 | MaxEmptyLinesToKeep: 2 22 | NamespaceIndentation: None 23 | PointerAlignment: Left 24 | SpacesBeforeTrailingComments: 0 25 | TabWidth: 2 26 | UseTab: Always 27 | 28 | PenaltyExcessCharacter: 1000000 29 | PenaltyReturnTypeOnItsOwnLine: 100000 30 | PenaltyBreakBeforeFirstCallParameter: 10000000 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/cmake,emacs,c,c++ 2 | 3 | ### CMake ### 4 | CMakeCache.txt 5 | CMakeFiles 6 | CMakeScripts 7 | Makefile 8 | cmake_install.cmake 9 | install_manifest.txt 10 | CTestTestfile.cmake 11 | 12 | 13 | ### Emacs ### 14 | # -*- mode: gitignore; -*- 15 | *~ 16 | \#*\# 17 | /.emacs.desktop 18 | /.emacs.desktop.lock 19 | *.elc 20 | auto-save-list 21 | tramp 22 | .\#* 23 | 24 | # Org-mode 25 | .org-id-locations 26 | *_archive 27 | 28 | # flymake-mode 29 | *_flymake.* 30 | 31 | # eshell files 32 | /eshell/history 33 | /eshell/lastdir 34 | 35 | # elpa packages 36 | /elpa/ 37 | 38 | # reftex files 39 | *.rel 40 | 41 | # AUCTeX auto folder 42 | /auto/ 43 | 44 | # cask packages 45 | .cask/ 46 | dist/ 47 | 48 | # Flycheck 49 | flycheck_*.el 50 | 51 | # server auth directory 52 | /server/ 53 | 54 | # projectiles files 55 | .projectile 56 | 57 | ### C ### 58 | # Prerequisites 59 | *.d 60 | 61 | # Object files 62 | *.o 63 | *.ko 64 | *.obj 65 | *.elf 66 | 67 | # Precompiled Headers 68 | *.gch 69 | *.pch 70 | 71 | # Libraries 72 | *.lib 73 | *.a 74 | *.la 75 | *.lo 76 | 77 | # Shared objects (inc. Windows DLLs) 78 | *.dll 79 | *.so 80 | *.so.* 81 | *.dylib 82 | 83 | # Executables 84 | *.exe 85 | *.out 86 | *.app 87 | *.i*86 88 | *.x86_64 89 | *.hex 90 | 91 | # Debug files 92 | *.dSYM/ 93 | *.su 94 | 95 | 96 | ### C++ ### 97 | # Prerequisites 98 | *.d 99 | 100 | # Compiled Object files 101 | *.slo 102 | *.lo 103 | *.o 104 | *.obj 105 | 106 | # Precompiled Headers 107 | *.gch 108 | *.pch 109 | 110 | # Compiled Dynamic libraries 111 | *.so 112 | *.dylib 113 | *.dll 114 | 115 | # Fortran module files 116 | *.mod 117 | *.smod 118 | 119 | # Compiled Static libraries 120 | *.lai 121 | *.la 122 | *.a 123 | *.lib 124 | 125 | # Executables 126 | *.exe 127 | *.out 128 | *.app 129 | 130 | ### Other ### 131 | build/ 132 | 133 | .tags* 134 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## PROJECT 3 | ########################################################### 4 | 5 | cmake_minimum_required(VERSION 3.2) 6 | project(tssx) 7 | 8 | set(CMAKE_MACOSX_RPATH 1) 9 | 10 | add_compile_options(-pthread) 11 | 12 | ########################################################### 13 | ## CONFIGURATION 14 | ########################################################### 15 | 16 | add_compile_options(-g -O3 -Wall -DDEBUG) 17 | # add_compile_options(-DNDEBUG) 18 | 19 | # Apple's C standard library already contains the math functions 20 | if(APPLE) 21 | add_compile_options(-std=c11) 22 | else() 23 | add_compile_options(-std=gnu11) 24 | link_libraries(m) 25 | endif() 26 | 27 | link_libraries(pthread) 28 | 29 | ########################################################### 30 | ## INCLUDES 31 | ########################################################### 32 | 33 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 34 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 35 | 36 | ########################################################### 37 | ## SUBDIRECTORIES 38 | ########################################################### 39 | 40 | add_subdirectory(source) 41 | add_subdirectory(try) 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Peter Goldsborough 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TSSX 2 | 3 | [![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg?style=flat-square)](http://goldsborough.mit-license.org) 4 | 5 | TSSX stands for *transparent shared-memory socket exchange* and is a system-level C library that silently replaces domain socket communication with a custom shared memory data channel, promising performance improvements up to an order of magnitude. 6 | 7 | ## Usage 8 | 9 | One of the core goals of TSSX is to be incredibly easy and hassle-free to integrate into your system. We use the [`LD_PRELOAD` trick](https://rafalcieslak.wordpress.com/2013/04/02/dynamic-linker-tricks-using-ld_preload-to-cheat-inject-features-and-investigate-programs/) to transparently overwrite system-call symbols with our own, using the dynamic linker. As such, if `./happy-banana-server` and `./happy-banana-client` are your executables using `write`/`read`, `send`/`recv` or similar system-calls to communicate over domains sockets, then the following lines will execute your application with TSSX: 10 | 11 | ```shell 12 | $ LD_PRELOAD=$PWD/path/to/libtssx-server.so ./happy-banana-server 13 | $ LD_PRELOAD=$PWD/path/to/libtssx-client.so ./happy-banana-client 14 | ``` 15 | 16 | where `libtssx-server.so` and `libtssx-client.so` are the result of compiling our library. And that is it! You don't have to recompile a single line, the dynamic linker does all the magic for you. We support the complete standard [Berkeley Socket API](https://en.wikipedia.org/wiki/Berkeley_sockets#Socket_API_functions), with light implementations of `fcntl` to the extent that is relevant to domain sockets. 17 | 18 | ## Compiling 19 | 20 | The project can be built using [CMake](http://cmake.org) on Linux and OS X: 21 | 22 | ```shell 23 | mkdir build 24 | cd build 25 | cmake .. 26 | make 27 | ``` 28 | 29 | Which will compile the TSSX library into the `build/source/tssx` path. We also provide example programs in the `try/` folder, compiled into `build/try`, with appropriate run scripts (for convenience) in the `scripts/` directory (run them from `build/try`). 30 | 31 | ## Publication 32 | 33 | We are working on a publication and will update this section accordingly in the near future. 34 | 35 | ## Authors 36 | 37 | TSSX is developed by [Peter Goldsborough](@goldsborough), [Alexander van Renen](https://db.in.tum.de/~vanrenen/?lang=de) and [Viktor Leis](https://www-db.in.tum.de/~leis/) at the [Chair for Database Systems](https://db.in.tum.de) of [Technical University of Munich (TUM)](http://tum.de). 38 | -------------------------------------------------------------------------------- /include/tssx/bridge.h: -------------------------------------------------------------------------------- 1 | #ifndef BRIDGE_H 2 | #define BRIDGE_H 3 | 4 | #include 5 | 6 | #include "tssx/definitions.h" 7 | #include "tssx/session-table.h" 8 | 9 | /******************** DEFINITIONS ********************/ 10 | 11 | #define BRIDGE_INITIALIZER \ 12 | { false, 0 } 13 | 14 | struct Session; 15 | 16 | /******************** STRUCTURES ********************/ 17 | 18 | // clang-format off 19 | typedef struct Bridge { 20 | bool is_initialized; 21 | size_t connection_count; 22 | SessionTable session_table; 23 | } Bridge; 24 | // clang-format on 25 | 26 | extern Bridge bridge; 27 | 28 | /******************** INTERFACE ********************/ 29 | 30 | int bridge_setup(Bridge* bridge); 31 | int bridge_destroy(Bridge* bridge); 32 | 33 | bool bridge_is_initialized(const Bridge* bridge); 34 | 35 | int bridge_add_user(Bridge* bridge); 36 | 37 | int bridge_insert(Bridge* bridge, int fd, struct Session* session); 38 | int bridge_erase(Bridge* bridge, int fd); 39 | 40 | struct Session* bridge_lookup(Bridge* bridge, int fd); 41 | bool bridge_has_connection(Bridge* bridge, int fd); 42 | bool bridge_has_any_connections(const Bridge* bridge); 43 | 44 | /******************** PRIVATE ********************/ 45 | 46 | extern signal_handler_t old_sigint_handler; 47 | extern signal_handler_t old_sigterm_handler; 48 | extern signal_handler_t old_sigabrt_handler; 49 | 50 | int _lazy_bridge_setup(Bridge* bridge); 51 | 52 | void _setup_exit_handling(); 53 | void _setup_signal_handler(int signal_number); 54 | 55 | void _bridge_signal_handler(); 56 | void _bridge_signal_handler_for(int signal_number, 57 | signal_handler_t old_handler); 58 | void _bridge_exit_handler(); 59 | 60 | #endif /* BRIDGE_H */ 61 | -------------------------------------------------------------------------------- /include/tssx/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_H 2 | #define BUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "tssx/timeouts.h" 10 | 11 | /******************** STRUCTURES ********************/ 12 | 13 | typedef struct Buffer { 14 | // The current size of the buffer (number of bytes used) 15 | atomic_int size; 16 | 17 | // The length of the memory segment in bytes 18 | size_t capacity; 19 | 20 | // Collection of timeout values 21 | Timeouts timeouts; 22 | 23 | // The read index 24 | size_t read; 25 | 26 | // The write index 27 | size_t write; 28 | 29 | } Buffer; 30 | 31 | /******************** INTERFACE ********************/ 32 | 33 | Buffer* create_buffer(void* shared_memory, 34 | size_t requested_capacity, 35 | const Timeouts* timeouts); 36 | 37 | size_t buffer_write(Buffer* buffer, const void* data, size_t data_size); 38 | size_t buffer_read(Buffer* buffer, void* data, size_t data_size); 39 | 40 | size_t buffer_peek(Buffer* buffer, void* data, size_t data_size); 41 | size_t buffer_skip(Buffer* buffer, size_t number_of_bytes); 42 | 43 | void buffer_clear(Buffer* buffer); 44 | 45 | bool buffer_is_full(Buffer* buffer); 46 | bool buffer_is_empty(Buffer* buffer); 47 | bool buffer_ready_for(Buffer* buffer, Operation operation); 48 | 49 | #ifdef TSSX_SUPPORT_BUFFER_TIMEOUTS 50 | void buffer_set_timeout(Buffer* buffer, 51 | Operation operation, 52 | cycle_t new_timeout); 53 | bool buffer_has_timeout(Buffer* buffer, Operation operation); 54 | #endif 55 | 56 | size_t buffer_free_space(Buffer* buffer); 57 | 58 | /******************** PRIVATE ********************/ 59 | 60 | typedef bool (*Condition)(Buffer*, size_t); 61 | 62 | void* _start_pointer(Buffer* buffer); 63 | void* _end_pointer(Buffer* buffer); 64 | void* _read_pointer(Buffer* buffer); 65 | void* _write_pointer(Buffer* buffer); 66 | 67 | void* _pointer_to(Buffer* buffer, size_t index); 68 | ptrdiff_t _index_at(Buffer* buffer, void* pointer); 69 | 70 | void _wrap_read(Buffer* buffer, void** data, size_t* data_size, size_t delta); 71 | void _wrap_write(Buffer* buffer, 72 | const void** data, 73 | size_t* data_size, 74 | size_t delta); 75 | void _reduce_data(const void** data, size_t* data_size, size_t delta); 76 | 77 | bool _timeout_elapsed(Buffer* buffer, cycle_t elapsed, Operation operation); 78 | bool _level_elapsed(Buffer* buffer, size_t level, cycle_t elapsed); 79 | 80 | void _pause(); 81 | cycle_t _now(); 82 | 83 | int _escalation_level(Buffer* buffer, cycle_t start_time, Operation operation); 84 | 85 | bool _ready_for_size(Buffer* buffer, 86 | Operation operation, 87 | size_t requested_size); 88 | int _block(Buffer* buffer, size_t requested_size, Operation operation); 89 | 90 | size_t _determine_available_space(Buffer* buffer, Operation operation); 91 | size_t _block_for_available_space(Buffer* buffer, Operation operation); 92 | size_t _get_available_space(Buffer* buffer, Operation operation); 93 | 94 | #endif /* BUFFER_H */ 95 | -------------------------------------------------------------------------------- /include/tssx/client-overrides.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIENT_OVERRIDES_H 2 | #define CLIENT_OVERRIDES_H 3 | 4 | #include "tssx/definitions.h" 5 | 6 | /******************** FORWARD DECLARATIONS ********************/ 7 | 8 | int _setup_tssx(int fd); 9 | int _read_segment_id_from_server(int fd); 10 | int _synchronize_with_server(int fd); 11 | 12 | #endif /* CLIENT_OVERRIDES_H */ 13 | -------------------------------------------------------------------------------- /include/tssx/common-overrides.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_OVERRIDES_H 2 | #define COMMON_OVERRIDES_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "utility/utility.h" 9 | #include "tssx/bridge.h" 10 | #include "tssx/buffer.h" 11 | #include "tssx/connection-options.h" 12 | #include "tssx/connection.h" 13 | #include "tssx/selective.h" 14 | #include "tssx/session.h" 15 | #include "tssx/shared-memory.h" 16 | 17 | #include "tssx/socket-overrides.h" 18 | 19 | /******************** DEFINITIONS ********************/ 20 | 21 | #define SERVER_BUFFER 0 22 | #define CLIENT_BUFFER 1 23 | 24 | struct Connection; 25 | struct Buffer; 26 | 27 | typedef int (*real_fcntl_t)(int, int, ...); 28 | typedef pid_t (*real_fork_t)(void); 29 | 30 | /******************** REAL FUNCTIONS ********************/ 31 | 32 | int real_fcntl_set_flags(int fd, int command, int flag); 33 | int real_fcntl_get_flags(int fd, int command); 34 | 35 | pid_t real_fork(void); 36 | 37 | /******************** COMMON OVERRIDES ********************/ 38 | 39 | pid_t fork(void); 40 | 41 | /******************** INTERFACE ********************/ 42 | 43 | ssize_t connection_write(int key, 44 | const void* source, 45 | size_t requested_bytes, 46 | int which_buffer); 47 | ssize_t connection_read(int key, 48 | void* destination, 49 | size_t requested_bytes, 50 | int which_buffer); 51 | 52 | int socket_is_stream_and_domain(int domain, int type); 53 | 54 | /******************** HELPERS ********************/ 55 | 56 | // Declarations only (defintions in server/client overrides) 57 | void set_non_blocking(Connection* connection, bool non_blocking); 58 | bool is_non_blocking(Connection* connection); 59 | 60 | int fcntl_set(int fd, int command, int flags); 61 | int fcntl_get(int fd, int command); 62 | 63 | struct Buffer* get_buffer(struct Connection* connection, int which_buffer); 64 | 65 | #endif /* COMMON_OVERRIDES_H */ 66 | -------------------------------------------------------------------------------- /include/tssx/common-poll-overrides.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_POLL_OVERRIDES_H 2 | #define COMMON_POLL_OVERRIDES_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "tssx/definitions.h" 11 | 12 | /******************** DEFINITIONS ********************/ 13 | 14 | #define POLL_SIGNAL SIGUSR1 15 | #define BLOCK_FOREVER -1 16 | #define DONT_BLOCK 0 17 | 18 | typedef void *(*thread_function_t)(void *); 19 | typedef atomic_int_fast16_t event_count_t; 20 | 21 | struct Connection; 22 | struct sigaction; 23 | 24 | /******************** HELPERS ********************/ 25 | 26 | bool _there_was_an_error(event_count_t *event_count); 27 | bool _poll_timeout_elapsed(size_t start, int timeout); 28 | 29 | int _install_poll_signal_handler(struct sigaction *old_action); 30 | int _restore_old_signal_action(struct sigaction *old_action); 31 | void _poll_signal_handler(int signal_number); 32 | void _kill_normal_thread(pthread_t normal_thread); 33 | 34 | int _set_poll_mask(pthread_mutex_t *lock, 35 | const sigset_t *sigmask, 36 | sigset_t *original_mask); 37 | int _restore_poll_mask(pthread_mutex_t *lock, const sigset_t *original_mask); 38 | 39 | /******************** "POLYMORPHIC" FUNCTIONS ********************/ 40 | 41 | bool _ready_for(struct Connection *entry, Operation operation); 42 | 43 | #endif /* COMMON_POLL_OVERRIDES_H */ 44 | -------------------------------------------------------------------------------- /include/tssx/connection-options.h: -------------------------------------------------------------------------------- 1 | #ifndef CONNECTION_OPTIONS_H 2 | #define CONNECTION_OPTIONS_H 3 | 4 | #include 5 | #include 6 | 7 | #include "tssx/definitions.h" 8 | #include "tssx/timeouts.h" 9 | 10 | /******************** DEFINITIONS ********************/ 11 | 12 | #define DEFAULT_BUFFER_SIZE 1000000 13 | 14 | // clang-format off 15 | #define DEFAULT_CONNECTION_OPTIONS_INITIALIZER \ 16 | { \ 17 | DEFAULT_BUFFER_SIZE, \ 18 | DEFAULT_TIMEOUTS_INITIALIZER, \ 19 | DEFAULT_BUFFER_SIZE, \ 20 | DEFAULT_TIMEOUTS_INITIALIZER, \ 21 | true \ 22 | } 23 | // clang-format on 24 | 25 | /******************** STRUCTURES ********************/ 26 | 27 | typedef struct ConnectionOptions { 28 | size_t server_buffer_size; 29 | Timeouts server_timeouts; 30 | 31 | size_t client_buffer_size; 32 | Timeouts client_timeouts; 33 | 34 | // Is the connection blocking ? 35 | bool isBlocking; 36 | 37 | } ConnectionOptions; 38 | 39 | extern const ConnectionOptions DEFAULT_OPTIONS; 40 | 41 | /******************** INTERFACE ********************/ 42 | 43 | ConnectionOptions options_from_socket(int socket_fd, Side side); 44 | size_t options_segment_size(const ConnectionOptions* options); 45 | 46 | /******************** PRIVATE ********************/ 47 | 48 | enum Direction; 49 | cycle_t socket_timeout_clocks(int socket_fd, enum Direction direction); 50 | 51 | #endif /* CONNECTION_OPTIONS_H */ 52 | -------------------------------------------------------------------------------- /include/tssx/connection.h: -------------------------------------------------------------------------------- 1 | #ifndef CONNECTION_H 2 | #define CONNECTION_H 3 | 4 | #include 5 | #include 6 | 7 | /******************** DEFINITIONS ********************/ 8 | 9 | #define CONNECTION_INITIALIZER \ 10 | { -1, NULL, NULL, NULL } 11 | 12 | struct ConnectionOptions; 13 | 14 | /******************** STRUCTURES ********************/ 15 | 16 | struct Buffer; 17 | 18 | typedef atomic_uint_fast16_t atomic_count_t; 19 | 20 | typedef struct Connection { 21 | // The ID of the shared memory 22 | int segment_id; 23 | 24 | // Reference count of open connections 25 | atomic_count_t* open_count; 26 | 27 | // The cast shared memory for the server buffer 28 | struct Buffer* server_buffer; 29 | 30 | // The cast shared memory for the client buffer 31 | struct Buffer* client_buffer; 32 | 33 | } Connection; 34 | 35 | /******************** INTERFACE ********************/ 36 | 37 | Connection* create_connection(const struct ConnectionOptions* options); 38 | Connection* setup_connection(int segment_id, 39 | const struct ConnectionOptions* options); 40 | 41 | void disconnect(Connection* connection); 42 | 43 | bool connection_peer_died(Connection* connection); 44 | void connection_add_user(Connection* connection); 45 | 46 | /******************** PRIVATE ********************/ 47 | 48 | void _create_server_buffer(Connection* connection, 49 | void* shared_memory, 50 | const struct ConnectionOptions* options); 51 | void _create_client_buffer(Connection* connection, 52 | void* shared_memory, 53 | const struct ConnectionOptions* options); 54 | 55 | void _retrieve_server_buffer(Connection* connection, void* shared_memory); 56 | void _retrieve_client_buffer(Connection* connection, void* shared_memory); 57 | 58 | void _init_open_count(Connection* connection, void* shared_memory); 59 | void _init_and_increment_open_count(Connection* connection, 60 | void* shared_memory); 61 | 62 | /******************** UTILITY ********************/ 63 | 64 | void _detach_connection(Connection* connection); 65 | void _destroy_connection(Connection* connection); 66 | 67 | void* _segment_start(Connection* connection); 68 | int _connection_segment_size(Connection* connection); 69 | void* _client_buffer_offset(Connection* connection, void* shared_memory); 70 | 71 | #endif /* CONNECTION_H */ 72 | -------------------------------------------------------------------------------- /include/tssx/definitions.h: -------------------------------------------------------------------------------- 1 | #ifndef DEFINITIONS_H 2 | #define DEFINITIONS_H 3 | 4 | #include 5 | 6 | // Common definitions to avoid having to include 7 | // whole headers when only these typedefs are needed 8 | 9 | #define ERROR -1 10 | #define SUCCESS 0 11 | 12 | typedef void (*signal_handler_t)(int); 13 | 14 | typedef int key_t; 15 | typedef uint_fast64_t cycle_t; 16 | 17 | typedef enum { SERVER, CLIENT } Side; 18 | typedef enum Operation { READ, WRITE } Operation; 19 | 20 | #endif /* DEFINITIONS_H */ 21 | -------------------------------------------------------------------------------- /include/tssx/epoll-overrides.h: -------------------------------------------------------------------------------- 1 | #ifndef EPOLL_OVERRIDES_H 2 | #define EPOLL_OVERRIDES_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "tssx/common-poll-overrides.h" 9 | #include "tssx/vector.h" 10 | 11 | /******************** DEFINITIONS ********************/ 12 | 13 | #define NUMBER_OF_EPOLL_INSTANCES 1024 14 | #define INITIAL_INSTANCE_CAPACITY 8 15 | 16 | /******************** REAL FUNCTIONS ********************/ 17 | 18 | typedef int (*real_epoll_create_t)(int); 19 | typedef int (*real_epoll_create1_t)(int); 20 | typedef int (*real_epoll_ctl_t)(int, int, int, struct epoll_event *); 21 | typedef int (*real_epoll_wait_t)(int, struct epoll_event *, int, int); 22 | 23 | // clang-format off 24 | typedef int (*real_epoll_pwait_t) 25 | (int, struct epoll_event *, int, int, const sigset_t *); 26 | // clang-format on 27 | 28 | /******************** STRUCTURES ********************/ 29 | 30 | struct Connection; 31 | 32 | typedef int epoll_operation_t; 33 | 34 | typedef struct EpollEntry { 35 | int fd; 36 | struct epoll_event event; 37 | Connection *connection; 38 | short flags; 39 | } EpollEntry; 40 | 41 | typedef struct EpollInstance { 42 | struct Vector entries; 43 | struct EpollEntry first; 44 | size_t tssx_count; 45 | size_t normal_count; 46 | } EpollInstance; 47 | 48 | typedef struct EpollTask { 49 | int epfd; 50 | struct epoll_event *events; 51 | size_t number_of_events; 52 | int timeout; 53 | event_count_t *shared_event_count; 54 | int event_count; 55 | } EpollTask; 56 | 57 | /******************** REAL FUNCTIONS ********************/ 58 | 59 | int real_epoll_create(int size); 60 | int real_epoll_create1(int flags); 61 | int real_epoll_ctl(int epfd, int operation, int fd, struct epoll_event *event); 62 | int real_epoll_wait(int epfd, 63 | struct epoll_event *events, 64 | int number_of_events, 65 | int timeout); 66 | int real_epoll_pwait(int epfd, 67 | struct epoll_event *events, 68 | int number_of_events, 69 | int timeout, 70 | const sigset_t *sigmask); 71 | 72 | /******************** OVERRIDES ********************/ 73 | 74 | int epoll_create(int size); 75 | int epoll_create1(int flags); 76 | 77 | int epoll_ctl(int epfd, int operation, int fd, struct epoll_event *event); 78 | 79 | int epoll_wait(int epfd, 80 | struct epoll_event *events, 81 | int number_of_events, 82 | int timeout); 83 | int epoll_pwait(int epfd, 84 | struct epoll_event *events, 85 | int number_of_events, 86 | int timeout, 87 | const sigset_t *sigmask); 88 | 89 | bool has_epoll_instance_associated(int epfd); 90 | 91 | int epoll_instance_size(int epfd); 92 | 93 | int close_epoll_instance(int epfd); 94 | 95 | /******************** PRIVATE DEFINITIONS ********************/ 96 | 97 | #define ENABLED 0x1 98 | #define READ_EDGE 0x2 99 | #define WRITE_EDGE 0x4 100 | 101 | extern epoll_operation_t _epoll_operation_map[2]; 102 | extern EpollInstance _epoll_instances[NUMBER_OF_EPOLL_INSTANCES]; 103 | extern bool _epoll_instances_are_initialized; 104 | 105 | extern pthread_mutex_t _epoll_lock; 106 | 107 | /******************** PRIVATE ********************/ 108 | 109 | int _setup_epoll_instances(); 110 | 111 | int _epoll_tssx_control_operation(int epfd, 112 | int operation, 113 | int fd, 114 | struct epoll_event *event, 115 | Session *session); 116 | 117 | int _epoll_normal_control_operation(int epfd, 118 | int operation, 119 | int fd, 120 | struct epoll_event *event); 121 | 122 | int _epoll_add_to_instance(EpollInstance *instance, 123 | int fd, 124 | struct epoll_event *event, 125 | Session *session); 126 | 127 | int _epoll_set_first_entry(EpollInstance *instance, 128 | int fd, 129 | struct epoll_event *event, 130 | Session *session); 131 | 132 | int _epoll_push_back_entry(EpollInstance *instance, 133 | int fd, 134 | struct epoll_event *event, 135 | Session *session); 136 | 137 | int _epoll_update_instance(EpollInstance *instance, 138 | int fd, 139 | const struct epoll_event *new_event); 140 | 141 | EpollEntry *_find_epoll_entry(EpollInstance *instance, int fd); 142 | 143 | int _epoll_erase_from_instance(EpollInstance *instance, int fd); 144 | int _epoll_erase_first_from_instance(EpollInstance *instance); 145 | 146 | int _simple_tssx_epoll_wait(EpollInstance *instance, 147 | struct epoll_event *events, 148 | size_t number_of_events, 149 | int timeout); 150 | 151 | int _concurrent_epoll_wait(int epfd, 152 | struct epoll_event *events, 153 | size_t number_of_events, 154 | int timeout); 155 | 156 | int _start_normal_epoll_wait_thread(pthread_t *normal_thread, EpollTask *task); 157 | void _normal_epoll_wait(EpollTask *task); 158 | int _concurrent_tssx_epoll_wait(EpollInstance *instance, 159 | struct epoll_event *events, 160 | size_t number_of_events, 161 | pthread_t normal_thread, 162 | event_count_t *shared_event_count, 163 | int timeout); 164 | 165 | int _tssx_epoll_wait_for_single_entry(EpollEntry *entry, 166 | struct epoll_event *events, 167 | size_t number_of_events, 168 | int timeout); 169 | 170 | bool _check_epoll_entry(EpollEntry *entry, 171 | struct epoll_event *events, 172 | size_t number_of_events, 173 | size_t event_count); 174 | bool _check_epoll_event(EpollEntry *entry, 175 | struct epoll_event *output_event, 176 | Operation operation); 177 | 178 | bool _epoll_operation_registered(EpollEntry *entry, size_t operation_index); 179 | bool _epoll_event_registered(const EpollEntry *entry, int event); 180 | 181 | bool _epoll_peer_died(EpollEntry *entry); 182 | 183 | void _notify_of_epoll_hangup(const EpollEntry *entry, 184 | struct epoll_event *output_event); 185 | 186 | Operation _convert_operation(size_t operation_index); 187 | void _invalid_argument_exception(); 188 | 189 | void _destroy_epoll_lock(); 190 | 191 | bool _is_edge_triggered(const EpollEntry *entry); 192 | bool _is_level_triggered(const EpollEntry *entry); 193 | 194 | bool _is_oneshot(const EpollEntry *entry); 195 | bool _is_edge_for(const EpollEntry *entry, Operation operation); 196 | bool _is_enabled(const EpollEntry *entry); 197 | 198 | void _set_poll_edge(EpollEntry *entry, Operation operation); 199 | void _clear_poll_edge(EpollEntry *entry, Operation operation); 200 | 201 | void _enable_poll_entry(EpollEntry *entry); 202 | void _disable_poll_entry(EpollEntry *entry); 203 | 204 | int _lazy_epoll_setup(); 205 | bool _lazy_epoll_setup_and_error(); 206 | int _validate_epoll_wait_arguments(int epfd, int number_of_events); 207 | 208 | #endif /* EPOLL_OVERRIDES_H */ 209 | -------------------------------------------------------------------------------- /include/tssx/free-list.h: -------------------------------------------------------------------------------- 1 | #ifndef FREE_LIST_H 2 | #define FREE_LIST_H 3 | 4 | #include 5 | #include 6 | 7 | #include "tssx/definitions.h" 8 | 9 | #define FREE_LIST_INITIALIZER VECTOR_INITIALIZER 10 | 11 | struct Vector; 12 | typedef struct Vector FreeList; 13 | 14 | void free_list_setup(FreeList* list); 15 | void free_list_destroy(FreeList* list); 16 | 17 | void free_list_push(FreeList* list, key_t key); 18 | int free_list_pop(FreeList* list); 19 | 20 | bool free_list_is_empty(FreeList* list); 21 | 22 | #endif /* FREE_LIST_H */ 23 | -------------------------------------------------------------------------------- /include/tssx/hashtable.h: -------------------------------------------------------------------------------- 1 | #ifndef HASHTABLE_H 2 | #define HASHTABLE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "connection.h" 8 | 9 | /***** DEFINITIONS *****/ 10 | 11 | #define HT_MINIMUM_CAPACITY 8 12 | #define HT_LOAD_FACTOR 5 13 | #define HT_MINIMUM_THRESHOLD (HT_MINIUM_CAPACITY) * (HT_LOAD_FACTOR) 14 | 15 | #define HT_UPDATED false 16 | #define HT_INSERTED true 17 | 18 | #define HT_NOT_FOUND false 19 | #define HT_FOUND true 20 | #define HT_OK true 21 | 22 | #define HT_UNINITIALIZED NULL 23 | 24 | #define HT_INITIALIZER {0, 0, 0, HT_UNINITIALIZED}; 25 | 26 | /***** STRUCTURES *****/ 27 | 28 | typedef struct Node { 29 | int key; 30 | Connection connection; 31 | 32 | struct Node* next; 33 | 34 | } Node; 35 | 36 | typedef struct HashTable { 37 | size_t size; 38 | size_t threshold; 39 | size_t capacity; 40 | 41 | // The node table 42 | Node** nodes; 43 | 44 | } HashTable; 45 | 46 | /***** METHODS *****/ 47 | 48 | void ht_setup(HashTable* table, size_t capacity); 49 | void ht_destroy(HashTable* table); 50 | 51 | bool ht_insert(HashTable* table, int key, Connection* connection); 52 | bool ht_contains(HashTable* table, int key); 53 | Connection* ht_get(HashTable* table, int key); 54 | 55 | bool ht_remove(HashTable* table, int key); 56 | void ht_clear(HashTable* table); 57 | 58 | bool ht_is_empty(HashTable* table); 59 | 60 | /***** PRIVATE *****/ 61 | 62 | void _ht_create_if_necessary(HashTable* table); 63 | 64 | void _ht_allocate(HashTable* table, size_t capacity); 65 | 66 | Node* _ht_create_node(int key, Connection* connection, Node* next); 67 | 68 | size_t _ht_hash(HashTable* table, int key); 69 | 70 | void _ht_resize(HashTable* table); 71 | 72 | void _ht_rehash(HashTable* table, Node** old, size_t old_capacity); 73 | 74 | #endif /* HASHTABLE_H */ 75 | -------------------------------------------------------------------------------- /include/tssx/poll-overrides.h: -------------------------------------------------------------------------------- 1 | #ifndef POLL_OVERRIDES_H 2 | #define POLL_OVERRIDES_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "tssx/common-poll-overrides.h" 11 | #include "tssx/definitions.h" 12 | 13 | /******************** DEFINITIONS ********************/ 14 | 15 | typedef int (*real_poll_t)(struct pollfd[], nfds_t, int); 16 | 17 | struct Connection; 18 | struct Vector; 19 | struct sigaction; 20 | 21 | typedef struct PollEntry { 22 | struct Connection *connection; 23 | struct pollfd *poll_pointer; 24 | } PollEntry; 25 | 26 | typedef struct PollTask { 27 | struct Vector *fds; 28 | int timeout; 29 | event_count_t *event_count; 30 | } PollTask; 31 | 32 | /******************** REAL FUNCTIONS ********************/ 33 | 34 | int real_poll(struct pollfd fds[], nfds_t nfds, int timeout); 35 | 36 | /******************** OVERRIDES ********************/ 37 | 38 | int poll(struct pollfd fds[], nfds_t number, int timeout); 39 | 40 | /******************** PRIVATE DEFINITIONS ********************/ 41 | 42 | extern const short _operation_map[2]; 43 | 44 | extern pthread_mutex_t _poll_lock; 45 | extern pthread_cond_t _poll_condition; 46 | extern bool _normal_thread_ready; 47 | extern bool _poll_is_initialized; 48 | 49 | /******************** HELPERS ********************/ 50 | 51 | int _partition(struct Vector *tssx_fds, 52 | struct Vector *normal_fds, 53 | struct pollfd fds[], 54 | nfds_t number); 55 | void _join_poll_partition(struct pollfd fds[], 56 | nfds_t number_of_fds, 57 | struct Vector *normal_fds); 58 | 59 | PollEntry _create_entry(struct pollfd *poll_pointer); 60 | 61 | int _start_normal_poll_thread(pthread_t *poll_thread, PollTask *task); 62 | 63 | int _simple_tssx_poll(struct Vector *tssx_fds, int timeout); 64 | 65 | int _concurrent_poll(struct Vector *tssx_fds, 66 | struct Vector *normal_fds, 67 | int timeout); 68 | void _normal_poll(PollTask *task); 69 | void _concurrent_tssx_poll(PollTask *task, pthread_t normal_thread); 70 | 71 | bool _check_poll_events(PollEntry *entry); 72 | bool _check_ready(PollEntry *entry, Operation operation); 73 | bool _waiting_for(PollEntry *entry, Operation operation); 74 | bool _tell_that_ready_for(PollEntry *entry, Operation operation); 75 | bool _entry_peer_died(PollEntry *entry); 76 | 77 | void _cleanup(struct Vector *tssx_fds, struct Vector *normal_fds); 78 | 79 | int _lazy_poll_setup(); 80 | int _setup_poll(); 81 | void _destroy_poll_lock_and_condvar(); 82 | 83 | int _signal_tssx_thread(); 84 | int _wait_for_normal_thread(); 85 | 86 | #endif /* POLL_OVERRIDES_H */ 87 | -------------------------------------------------------------------------------- /include/tssx/reverse-map.h: -------------------------------------------------------------------------------- 1 | #ifndef REVERSE_MAP_H 2 | #define REVERSE_MAP_H 3 | 4 | #include 5 | #include 6 | 7 | #include "definitions.h" 8 | 9 | /* 10 | * The point of this is to hide the implementation of the underlying 11 | * reverse-map container via functions. If we just used the array *currently* 12 | * used below as the container, then we'd have to do array indexing. However, 13 | * we might decide at a later point that using some other data structure, such 14 | * as a (hash) set maybe, would be better. Then we'd have to recode all our 15 | * accesses. This way, we can just change the implementations of the methods 16 | * and no external code using this interface must be touched (profit) 17 | */ 18 | 19 | /******************** DEFINITIONS ********************/ 20 | 21 | #define REVERSE_MAP_ERROR 0 22 | #define REVERSE_MAP_SUCCESS 1 23 | 24 | #define REVERSE_MAP_SIZE FD_SETSIZE 25 | #define INVALID_KEY 0 26 | 27 | typedef key_t ReverseMap[REVERSE_MAP_SIZE]; 28 | 29 | /******************** INTERFACE ********************/ 30 | 31 | int reverse_map_setup(ReverseMap* reverse); 32 | int reverse_map_destroy(ReverseMap* reverse); 33 | 34 | key_t reverse_map_lookup(ReverseMap* reverse, int socket_fd); 35 | int reverse_map_has_entry_for(ReverseMap* reverse, int socket_fd); 36 | 37 | int reverse_map_erase(ReverseMap* reverse, int socket_fd); 38 | int reverse_map_insert(ReverseMap* reverse, int socket_fd, int fd); 39 | 40 | ssize_t reverse_map_size(const ReverseMap* map); 41 | int reverse_map_is_empty(const ReverseMap* map); 42 | 43 | /******************** PRIVATE ********************/ 44 | 45 | #endif /* REVERSE_MAP_H */ 46 | -------------------------------------------------------------------------------- /include/tssx/select-overrides.h: -------------------------------------------------------------------------------- 1 | #ifndef SELECT_OVERRIDES_H 2 | #define SELECT_OVERRIDES_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "tssx/definitions.h" 11 | 12 | /******************** DEFINITIONS ********************/ 13 | 14 | // clang-format off 15 | typedef int (*real_select_t)( 16 | int, fd_set *, fd_set *, fd_set *, struct timeval * 17 | ); 18 | 19 | typedef int (*real_pselect_t)( 20 | int, fd_set *, fd_set *, fd_set *, 21 | const struct timespec *, const sigset_t * 22 | ); 23 | // clang-format on 24 | 25 | struct pollfd; 26 | struct timespec; 27 | 28 | typedef struct DescriptorSets { 29 | fd_set *readfds; 30 | fd_set *writefds; 31 | fd_set *errorfds; 32 | } DescriptorSets; 33 | 34 | /******************** REAL FUNCTIONS ********************/ 35 | 36 | int real_select(int nfds, 37 | fd_set *readfds, 38 | fd_set *writefds, 39 | fd_set *errorfds, 40 | struct timeval *timeout); 41 | 42 | int real_pselect(int nfds, 43 | fd_set *readfds, 44 | fd_set *writefds, 45 | fd_set *errorfds, 46 | const struct timespec *timeout, 47 | const sigset_t *sigmask); 48 | 49 | /******************** OVERRIDES ********************/ 50 | 51 | int select(int nfds, 52 | fd_set *readfds, 53 | fd_set *writefds, 54 | fd_set *errorfds, 55 | struct timeval *timeout); 56 | 57 | int pselect(int nfds, 58 | fd_set *readfds, 59 | fd_set *writefds, 60 | fd_set *errorfds, 61 | const struct timespec *timeout, 62 | const sigset_t *sigmask); 63 | 64 | /******************** PRIVATE DEFINITIONS ********************/ 65 | 66 | extern pthread_mutex_t _select_lock; 67 | extern bool _select_is_initialized; 68 | 69 | /******************** HELPERS ********************/ 70 | 71 | int _forward_to_poll(size_t highest_fd, 72 | DescriptorSets *sets, 73 | size_t population_count, 74 | struct timeval *timeout); 75 | 76 | struct pollfd *_setup_poll_entries(size_t population_count, 77 | const DescriptorSets *sets, 78 | size_t highest_fd); 79 | void _fill_poll_entries(struct pollfd *poll_entries, 80 | const DescriptorSets *sets, 81 | size_t highest_fd); 82 | 83 | int _read_poll_entries(DescriptorSets *sets, 84 | struct pollfd *poll_entries, 85 | size_t population_count); 86 | 87 | int _select_on_tssx_only(DescriptorSets *sets, 88 | size_t tssx_count, 89 | size_t lowest_fd, 90 | size_t highest_fd, 91 | struct timeval *timeout); 92 | 93 | int _select_on_tssx_only_fast_path(DescriptorSets *sets, 94 | size_t fd, 95 | struct timeval *timeout); 96 | 97 | void _count_tssx_sockets(size_t highest_fd, 98 | const DescriptorSets *sets, 99 | size_t *lowest_fd, 100 | size_t *normal_count, 101 | size_t *tssx_count); 102 | 103 | bool _is_in_any_set(int fd, const DescriptorSets *sets); 104 | 105 | void _copy_all_sets(DescriptorSets *destination, const DescriptorSets *source); 106 | void _copy_set(fd_set *destination, const fd_set *source); 107 | 108 | void _clear_all_sets(DescriptorSets *sets); 109 | bool _fd_is_set(int fd, const fd_set *set); 110 | 111 | bool _check_select_events(int fd, Session *session, DescriptorSets *sets); 112 | 113 | bool _select_peer_died(int fd, Session *session, DescriptorSets *sets); 114 | 115 | bool _waiting_and_ready_for_select(int fd, 116 | Session *session, 117 | const DescriptorSets *sets, 118 | Operation operation); 119 | 120 | bool _ready_for_select(int fd, 121 | Session *session, 122 | fd_set *set, 123 | Operation operation); 124 | 125 | bool _check_poll_event_occurred(const struct pollfd *entry, 126 | DescriptorSets *sets, 127 | int event); 128 | 129 | fd_set *_fd_set_for_operation(const DescriptorSets *sets, Operation operation); 130 | fd_set *_fd_set_for_poll_event(const DescriptorSets *sets, int poll_event); 131 | 132 | fd_set *_fd_set_for_operation(const DescriptorSets *sets, Operation operation); 133 | fd_set *_fd_set_for_poll_event(const DescriptorSets *sets, int poll_event); 134 | 135 | bool _select_timeout_elapsed(size_t start, int timeout); 136 | 137 | void _clear_set(fd_set *set); 138 | 139 | int _lazy_select_setup(); 140 | int _setup_select(); 141 | void _destroy_select_lock(); 142 | 143 | #endif /* SELECT_OVERRIDES_H */ 144 | -------------------------------------------------------------------------------- /include/tssx/selective.h: -------------------------------------------------------------------------------- 1 | #ifndef SELECTIVE_H 2 | #define SELECTIVE_H 3 | 4 | #include "tssx/definitions.h" 5 | 6 | /******************** DEFINITIONS ********************/ 7 | 8 | #define ERROR -1 9 | 10 | struct StringSet; 11 | struct sockaddr; 12 | struct sockaddr_un; 13 | struct sockaddr_storage; 14 | 15 | extern struct StringSet selective_set; 16 | 17 | /******************** INTERFACE ********************/ 18 | 19 | int check_tssx_usage(int fd, Side side); 20 | 21 | /******************** PRIVATE ********************/ 22 | 23 | int _in_selective_set(struct sockaddr_storage* address, size_t length); 24 | int _is_domain_and_stream_socket(int fd, struct sockaddr_storage* address); 25 | int _is_domain_socket(struct sockaddr_storage* address); 26 | int _is_stream_socket(int fd); 27 | 28 | void _initialize_selective_set(); 29 | 30 | const char* _fetch_tssx_variable(); 31 | void _parse_tssx_variable(const char* variable); 32 | 33 | int _get_socket_option(int fd, int option_name); 34 | int _get_socket_address(int fd, struct sockaddr_storage* address, Side side); 35 | 36 | void _insert_null_terminator(struct sockaddr_un* address, size_t length); 37 | 38 | #endif /* SELECTIVE_H */ 39 | -------------------------------------------------------------------------------- /include/tssx/server-overrides.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_OVERRIDES_H 2 | #define SERVER_OVERRIDES_H 3 | 4 | /******************** DEFINITIONS ********************/ 5 | 6 | struct Session; 7 | 8 | /******************** FORWARD DECLARATIONS ********************/ 9 | 10 | int _setup_tssx(int client_socket); 11 | int _send_segment_id_to_client(int client_socket, struct Session* session); 12 | int _synchronize_with_client(int client_fd); 13 | 14 | #endif /* SERVER_OVERRIDES_H */ 15 | -------------------------------------------------------------------------------- /include/tssx/session-table.h: -------------------------------------------------------------------------------- 1 | #ifndef SESSION_TABLE_H 2 | #define SESSION_TABLE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "tssx/session.h" 9 | 10 | /******************** DEFINITIONS ********************/ 11 | 12 | #define SESSION_TABLE_SIZE FD_SETSIZE// Usually 1024 13 | 14 | typedef Session SessionTable[SESSION_TABLE_SIZE]; 15 | 16 | /******************** INTERFACE ********************/ 17 | 18 | void session_table_setup(SessionTable* table); 19 | void session_table_destroy(SessionTable* table); 20 | 21 | void session_table_assign(SessionTable* table, 22 | size_t index, 23 | struct Session* session); 24 | struct Session* session_table_get(SessionTable* table, size_t index); 25 | 26 | #endif /* SESSION_TABLE_H */ 27 | -------------------------------------------------------------------------------- /include/tssx/session.h: -------------------------------------------------------------------------------- 1 | #ifndef SESSION_H 2 | #define SESSION_H 3 | 4 | #include 5 | 6 | 7 | /******************** DEFINITIONS ********************/ 8 | 9 | #define SESSION_INITIALIZER \ 10 | { NULL } 11 | 12 | struct Connection; 13 | 14 | /******************** STRUCTURES ********************/ 15 | 16 | /** 17 | * This structure is there in case future changes require 18 | * a modification to the session (an element in the bridge) 19 | * as it was necessary before. 20 | */ 21 | // clang-format off 22 | typedef struct Session { 23 | struct Connection* connection; 24 | } Session; 25 | // clang-format on 26 | 27 | /******************** INTERFACE ********************/ 28 | 29 | void session_setup(Session* session); 30 | 31 | bool session_has_connection(const Session* session); 32 | void session_invalidate(Session* session); 33 | 34 | #endif /* SESSION_H */ 35 | -------------------------------------------------------------------------------- /include/tssx/shared-memory.h: -------------------------------------------------------------------------------- 1 | #ifndef SHARED_MEMORY_H 2 | #define SHARED_MEMORY_H 3 | 4 | struct Buffer; 5 | 6 | // Returns the ID 7 | int create_segment(int total_size); 8 | 9 | // Returns the shared memory 10 | void* attach_segment(int segment_id); 11 | 12 | void detach_segment(void* shared_memory); 13 | 14 | void destroy_segment(int segment_id); 15 | 16 | int segment_size(struct Buffer* buffer); 17 | 18 | #endif /* SHARED_MEMORY_H */ 19 | -------------------------------------------------------------------------------- /include/tssx/socket-overrides.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_OVERRIDES_H 2 | #define SOCKET_OVERRIDES_H 3 | 4 | #include 5 | 6 | /******************** DEFINITIONS ********************/ 7 | 8 | typedef struct sockaddr sockaddr; 9 | typedef struct msghdr msghdr; 10 | 11 | typedef unsigned int socklen_t; 12 | 13 | typedef int (*real_accept_t)(int, sockaddr*, socklen_t*); 14 | typedef int (*real_connect_t)(int, const sockaddr*, socklen_t); 15 | 16 | typedef ssize_t (*real_write_t)(int, const void*, size_t); 17 | typedef ssize_t (*real_read_t)(int, void*, size_t); 18 | 19 | typedef ssize_t (*real_send_t)(int, const void*, size_t, int); 20 | typedef ssize_t (*real_recv_t)(int, void*, size_t, int); 21 | 22 | typedef ssize_t (*real_sendmsg_t)(int, const struct msghdr*, int); 23 | typedef ssize_t (*real_recvmsg_t)(int, struct msghdr*, int); 24 | 25 | // clang-format off 26 | typedef ssize_t (*real_sendto_t)(int, 27 | const void*, 28 | size_t, 29 | int, 30 | const struct sockaddr*, 31 | socklen_t); 32 | typedef ssize_t (*real_recvfrom_t)(int fd, 33 | void* restrict buffer, 34 | size_t length, 35 | int flags, 36 | struct sockaddr* restrict address, 37 | socklen_t* restrict address_len); 38 | // clang-format on 39 | 40 | typedef int (*real_close_t)(int); 41 | 42 | // clang-format off 43 | typedef int (*real_getsockopt_t) 44 | (int, int, int, void* restrict, socklen_t* restrict); 45 | // clang-format on 46 | typedef int (*real_setsockopt_t)(int, int, int, const void*, socklen_t); 47 | typedef int (*real_getsockname_t)(int, struct sockaddr*, socklen_t*); 48 | 49 | /******************** REAL FUNCTIONS ********************/ 50 | 51 | ssize_t real_write(int fd, const void* data, size_t size); 52 | ssize_t real_read(int fd, void* data, size_t size); 53 | 54 | ssize_t real_send(int fd, const void* buffer, size_t length, int flags); 55 | ssize_t real_recv(int fd, void* buffer, size_t length, int flags); 56 | 57 | ssize_t real_sendmsg(int fd, const struct msghdr* message, int flags); 58 | ssize_t real_recvmsg(int fd, struct msghdr* message, int flags); 59 | 60 | ssize_t real_sendto(int fd, 61 | const void* buffer, 62 | size_t length, 63 | int flags, 64 | const struct sockaddr* dest_addr, 65 | socklen_t dest_len); 66 | ssize_t real_recvfrom(int fd, 67 | void* restrict buffer, 68 | size_t length, 69 | int flags, 70 | struct sockaddr* restrict address, 71 | socklen_t* restrict address_len); 72 | 73 | 74 | int real_accept(int fd, sockaddr* address, socklen_t* length); 75 | int real_connect(int fd, const sockaddr* address, socklen_t length); 76 | 77 | int real_getsockopt(int fd, 78 | int level, 79 | int option_name, 80 | void* restrict option_value, 81 | socklen_t* restrict option_len); 82 | 83 | int real_setsockopt(int fd, 84 | int level, 85 | int option_name, 86 | const void* option_value, 87 | socklen_t option_len); 88 | 89 | int real_close(int fd); 90 | 91 | /******************** COMMON OVERRIDES ********************/ 92 | 93 | int getsockopt(int fd, 94 | int level, 95 | int option_name, 96 | void* restrict option_value, 97 | socklen_t* restrict option_len); 98 | 99 | int setsockopt(int fd, 100 | int level, 101 | int option_name, 102 | const void* option_value, 103 | socklen_t option_len); 104 | 105 | #endif /* SOCKET_OVERRIDES_H */ 106 | -------------------------------------------------------------------------------- /include/tssx/string-set.h: -------------------------------------------------------------------------------- 1 | #ifndef STRING_SET_H 2 | #define STRING_SET_H 3 | 4 | #include 5 | #include 6 | 7 | /***** DEFINITIONS *****/ 8 | 9 | #define SS_MINIMUM_CAPACITY 8 10 | #define SS_LOAD_FACTOR 5 11 | #define SS_MINIMUM_THRESHOLD (SS_MINIUM_CAPACITY) * (SS_LOAD_FACTOR) 12 | 13 | #define SS_NOT_INSERTED false 14 | #define SS_INSERTED true 15 | 16 | #define SS_NOT_FOUND false 17 | #define SS_FOUND true 18 | #define SS_OK true 19 | 20 | #define SS_UNINITIALIZED NULL 21 | 22 | #define SS_INITIALIZER {0, 0, 0, SS_UNINITIALIZED}; 23 | 24 | typedef const char* String; 25 | 26 | /***** STRUCTURES *****/ 27 | 28 | typedef struct Node { 29 | struct Node* next; 30 | 31 | char key[];// flexible arrray 32 | 33 | } Node; 34 | 35 | typedef struct StringSet { 36 | size_t size; 37 | size_t threshold; 38 | size_t capacity; 39 | 40 | // The node set 41 | Node** nodes; 42 | 43 | } StringSet; 44 | 45 | /***** METHODS *****/ 46 | 47 | void ss_setup(StringSet* set, size_t capacity); 48 | void ss_destroy(StringSet* set); 49 | 50 | bool ss_insert(StringSet* set, String key); 51 | bool ss_remove(StringSet* set, String key); 52 | bool ss_contains(StringSet* set, String key); 53 | void ss_clear(StringSet* set); 54 | 55 | bool ss_is_empty(StringSet* set); 56 | bool ss_is_initialized(StringSet* set); 57 | 58 | /***** PRIVATE *****/ 59 | 60 | bool _ss_equals(String first, String second); 61 | 62 | void _ss_create_if_necessary(StringSet* set); 63 | 64 | void _ss_allocate(StringSet* set, size_t capacity); 65 | 66 | Node* _ss_create_node(String key, Node* next); 67 | 68 | size_t _ss_hash(StringSet* set, String key); 69 | 70 | void _ss_resize(StringSet* set); 71 | 72 | void _ss_rehash(StringSet* set, Node** old, size_t old_capacity); 73 | 74 | #endif /* HASHTABLE_H */ 75 | -------------------------------------------------------------------------------- /include/tssx/timeouts.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMEOUTS_H 2 | #define TIMEOUTS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "definitions.h" 9 | 10 | /******************** DEFINITIONS ********************/ 11 | 12 | #define INFINITE 0 13 | 14 | // Different timeout levels 15 | #define TIMEOUT -1 16 | #define LEVEL_ZERO 0 17 | #define LEVEL_ONE 1 18 | #define LEVEL_TWO 2 19 | 20 | // Conversion notes: 21 | // 1 clock cycle ~ 0.25 ns (assuming 4Ghz = 1/(4 * 10^9)) 22 | // 1000 clock cycles ~ 250 ns 23 | // 1 pause instruction ~ 40 clock cyles ~ 10 ns 24 | // 1000 clock cycles ~ 250 ns ~ 25 pause instructions 25 | 26 | #define DEFAULT_LEVEL_ZERO_CLOCKS (10 * 000) // ~2500 ns, 250 pauses 27 | #define DEFAULT_LEVEL_ONE_CLOCKS (100 * 1000)// ~25 us 28 | 29 | #define SECONDS_TO_CLOCKS(seconds) ((cycle_t)((seconds)*CLOCKS_PER_SEC)) 30 | #define CLOCKS_TO_SECONDS(clock_cycles) (((double)seconds) / CLOCKS_PER_SEC) 31 | 32 | // clang-format off 33 | #ifdef TSSX_SUPPORT_BUFFER_TIMEOUTS 34 | #define DEFAULT_TIMEOUTS_INITIALIZER \ 35 | { \ 36 | {false, false}, \ 37 | { DEFAULT_LEVEL_ZERO_CLOCKS, DEFAULT_LEVEL_ONE_CLOCKS }, \ 38 | {INFINITE, INFINITE } \ 39 | } 40 | #else 41 | #define DEFAULT_TIMEOUTS_INITIALIZER \ 42 | { \ 43 | {false, false}, \ 44 | { DEFAULT_LEVEL_ZERO_CLOCKS, DEFAULT_LEVEL_ONE_CLOCKS } \ 45 | } 46 | #endif 47 | // clang-format on 48 | 49 | /******************** STRUCTURES ********************/ 50 | 51 | typedef struct Timeouts { 52 | bool non_blocking[2]; 53 | cycle_t levels[2]; 54 | 55 | #ifdef TSSX_SUPPORT_BUFFER_TIMEOUTS 56 | cycle_t timeout[2]; 57 | #endif 58 | } Timeouts; 59 | 60 | extern const Timeouts DEFAULT_TIMEOUTS; 61 | 62 | /******************** INTERFACE ********************/ 63 | 64 | 65 | #endif /* TIMEOUTS_H */ 66 | -------------------------------------------------------------------------------- /include/utility/arguments.h: -------------------------------------------------------------------------------- 1 | #ifndef IPC_BENCH_ARGUMENTS_H 2 | #define IPC_BENCH_ARGUMENTS_H 3 | 4 | #define DEFAULT_MESSAGE_SIZE 4096 5 | 6 | void print_usage(); 7 | 8 | typedef struct Arguments { 9 | int size; 10 | int count; 11 | 12 | } Arguments; 13 | 14 | void parse_arguments(Arguments* arguments, int argc, char* argv[]); 15 | 16 | int check_flag(const char* name, int argc, char* argv[]); 17 | 18 | #endif /* IPC_BENCH_ARGUMENTS_H */ 19 | -------------------------------------------------------------------------------- /include/utility/benchmarks.h: -------------------------------------------------------------------------------- 1 | #ifndef IPC_BENCH_BENCHMARKS_H 2 | #define IPC_BENCH_BENCHMARKS_H 3 | 4 | struct Arguments; 5 | 6 | typedef unsigned long bench_t; 7 | 8 | typedef struct Benchmarks { 9 | // Start of the total benchmarking 10 | bench_t total_start; 11 | 12 | // Start of single benchmark 13 | bench_t single_start; 14 | 15 | // Minimum time 16 | bench_t minimum; 17 | 18 | // Maximum time 19 | bench_t maximum; 20 | 21 | // Sum (for averaging) 22 | bench_t sum; 23 | 24 | // Squared sum (for standard deviation) 25 | bench_t squared_sum; 26 | 27 | } Benchmarks; 28 | 29 | bench_t now(); 30 | 31 | void setup_benchmarks(Benchmarks *bench); 32 | 33 | void benchmark(Benchmarks *bench); 34 | 35 | void evaluate(Benchmarks *bench, struct Arguments *args); 36 | 37 | #endif /* IPC_BENCH_BENCHMARKS_H */ 38 | -------------------------------------------------------------------------------- /include/utility/common.h: -------------------------------------------------------------------------------- 1 | #ifndef IPC_BENCH_COMMON_H 2 | #define IPC_BENCH_COMMON_H 3 | 4 | #include "utility/arguments.h" 5 | #include "utility/benchmarks.h" 6 | #include "utility/signals.h" 7 | #include "utility/utility.h" 8 | 9 | #endif /* IPC_BENCH_COMMON_H */ 10 | -------------------------------------------------------------------------------- /include/utility/parent.h: -------------------------------------------------------------------------------- 1 | #ifndef IPC_BENCH_PARENT_H 2 | #define IPC_BENCH_PARENT_H 3 | 4 | void setup_parent(char* name, int argc, char* argv[]); 5 | 6 | #endif /* IPC_BENCH_PARENT_H */ 7 | -------------------------------------------------------------------------------- /include/utility/process.h: -------------------------------------------------------------------------------- 1 | #ifndef IPC_BENCH_PROCESS_H 2 | #define IPC_BENCH_PROCESS_H 3 | 4 | char *find_build_path(); 5 | 6 | void start_process(char *argv[]); 7 | 8 | void copy_arguments(char *arguments[], int argc, char *argv[]); 9 | 10 | void start_child(char *name, int argc, char *argv[]); 11 | 12 | void start_children(char *prefix, int argc, char *argv[]); 13 | 14 | #endif /* IPC_BENCH_PROCESS_H */ 15 | -------------------------------------------------------------------------------- /include/utility/signals.h: -------------------------------------------------------------------------------- 1 | #ifndef IPC_BENCH_SIGNALS_H 2 | #define IPC_BENCH_SIGNALS_H 3 | 4 | #include 5 | 6 | #define IGNORE_USR1 0x0 7 | #define IGNORE_USR2 0x0 8 | #define BLOCK_USR1 0x1 9 | #define BLOCK_USR2 0x2 10 | 11 | #define WAIT 0x0 12 | #define NOTIFY 0x1 13 | 14 | struct sigaction; 15 | 16 | void signal_handler(int _); 17 | 18 | void setup_signals(struct sigaction *signal_action, int flags); 19 | void setup_parent_signals(); 20 | void setup_server_signals(struct sigaction *signal_action); 21 | void setup_client_signals(struct sigaction *signal_action); 22 | 23 | void notify_server(); 24 | void notify_client(); 25 | 26 | void wait_for_signal(struct sigaction *signal_action); 27 | 28 | void client_once(int operation); 29 | void server_once(int operation); 30 | 31 | #endif /* IPC_BENCH_SIGNALS_H */ 32 | -------------------------------------------------------------------------------- /include/utility/sockets.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKETS_H 2 | #define SOCKETS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /******************** DEFINITIONS ********************/ 9 | 10 | #define BUFFER_SIZE 64000 11 | 12 | typedef enum Direction { SEND, RECEIVE } Direction; 13 | 14 | struct timeval; 15 | typedef struct timeval timeval; 16 | 17 | /******************** INTERFACE ********************/ 18 | 19 | int socket_buffer_size(int socket_fd, Direction direction); 20 | 21 | void set_socket_buffer_size(int socket_fd, Direction direction); 22 | void set_socket_both_buffer_sizes(int socket_fd); 23 | 24 | timeval socket_timeout(int socket_fd, Direction direction); 25 | double socket_timeout_seconds(int socket_fd, Direction direction); 26 | 27 | void set_socket_timeout(int socket_fd, timeval* timeout, Direction direction); 28 | void set_socket_both_timeouts(int socket_fd, int seconds, int microseconds); 29 | 30 | int get_socket_flags(int socket_fd); 31 | void set_socket_flags(int socket_fd, int flags); 32 | 33 | int set_socket_non_blocking(int socket_fd); 34 | int unset_socket_non_blocking(int socket_fd); 35 | 36 | bool socket_is_non_blocking(int socket_fd); 37 | 38 | int set_io_flag(int socket_fd, int flag); 39 | 40 | int receive(int connection, void* buffer, int size, int busy_waiting); 41 | 42 | #endif /* SOCKETS_H */ 43 | -------------------------------------------------------------------------------- /include/utility/utility.h: -------------------------------------------------------------------------------- 1 | #ifndef IPC_BENCH_UTILITY_H 2 | #define IPC_BENCH_UTILITY_H 3 | 4 | #ifdef DEBUG 5 | #include 6 | #endif 7 | 8 | #include 9 | 10 | /******************** DEFINITIONS ********************/ 11 | 12 | struct timeval; 13 | struct timespec; 14 | 15 | /******************** INTERFACE ********************/ 16 | 17 | /** 18 | * Calls perror() and exits the program. 19 | * 20 | * Use this function when the error *is* the result of a syscall failure. Then 21 | * it will print the specified message along with the implementation defined 22 | * error message that is appended in perror(). Do not append a newline. 23 | * 24 | * \param message The message to print. 25 | * 26 | * \see terminate() 27 | */ 28 | void throw(const char* message) __attribute__((noreturn)); 29 | 30 | /** 31 | * Prints a message to stderr and exits the program. 32 | * 33 | * Use this function when the error is not the result of a syscall failure. Do 34 | * append a newline to the message. 35 | * 36 | * \param message The message to print. 37 | * 38 | * \see throw() 39 | */ 40 | void terminate(const char* message) __attribute__((noreturn)); 41 | 42 | /** 43 | * Prints a message to stderr. 44 | * 45 | * param message The message to print. 46 | */ 47 | void print_error(const char* message); 48 | 49 | /** 50 | * Prints "Warning: ". 51 | * 52 | * \param message The warning message. 53 | */ 54 | void warn(const char* message); 55 | 56 | int generate_key(const char* path); 57 | 58 | void nsleep(int nanoseconds); 59 | 60 | int current_milliseconds(); 61 | int timeval_to_milliseconds(const struct timeval* time); 62 | void timespec_to_timeval(const struct timespec* source, 63 | struct timeval* destination); 64 | 65 | void pin_thread(int where); 66 | 67 | #endif /* IPC_BENCH_UTILITY_H */ 68 | -------------------------------------------------------------------------------- /other/notes.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## Dynamic TSSX/No-TSSX detection 4 | 5 | ### First Approach 6 | 7 | Inside `accept`, we first create a small shared memory segment with only two atomic bools that indicate TSSX awareness. We then set the server flag to true in this segment. On the client side, inside connect, the client could detect this flag being set to true, set its own flag to true and then allocate the shared memory segment (maybe reverse roles). Inside write (or read then), e.g. on server side (o.B.d.A.): 8 | 9 | 1. Is the client's tssx flag set? 10 | 1. If not, use `real_write` since the client is not using tssx. 11 | 2. Else, have we attached (not allocated) the segment to our address space yet? 12 | 1. If not, attach it. 13 | 2. Either way, call `buffer_write` 14 | 15 | This may make the very first write/read expensive, but makes the rest work fab. 16 | 17 | ### Second Approach 18 | 19 | The above will probably not work properly because the shared memory key must be 20 | known by both (all processes), since we can't pass the key via a socket as we 21 | do for the connection segments (where we know that the other process is 22 | cooperating). Rather, we'll have to have a global "registry" file (as in, a 23 | document file in the file system) where we associate the domain socket path with 24 | two bits for the client and server. In the simplest case, this could be a file 25 | `/tmp/tssx.registry` with the following format: 26 | 27 | ``` 28 | 29 | ``` 30 | 31 | However, this would require linear lookups and in-file stream processing as the 32 | file would have to be synchronized with the connections being opened and closed 33 | by any process. Much rather, we could simply the two bits with every socket in a 34 | *separate file*. For example, say the domain socket path is 35 | `/tmp/domain-socket`. Then we'll have a `/tmp/registry/domain-socket` or 36 | `/tmp/domain-socket.tssx` file, storing just a single byte (for the two 37 | bits). This would allow very fast lookup and updates, since we have a direct 38 | bijection between domain sockets and the associated registry files. Then, on 39 | either side, we would check if the registry file exist and optionally create 40 | it. Either way, we then load the file and update one of the two bits according 41 | to the side on which we are (server/client). For this, we'd need a file-locking 42 | mechanism (posix file locks) to ensure synchronization. But basically, we'd have 43 | to first check to create the registry, and then update it: 44 | 45 | ``` 46 | if (registry does not exist yet) { 47 | result = try_to_create_registry(); 48 | if (result == ERROR) return error; 49 | // else we either sucessfully created the registry (folder/file) 50 | // or we had a race condition but the other side was quicker to 51 | // create the file in exlusive mode 52 | } 53 | 54 | lock_file(); 55 | bits = load_registry_bits(); 56 | bits |= bit_for_this_side; 57 | store_registry_bits(bits); 58 | unlock_file(); 59 | ``` 60 | 61 | We could then check the bits the first time we `write()` or `read()`. We just 62 | have to be really careful about race conditions. 63 | 64 | ### Third Approach 65 | 66 | Shared Memory Segments could indeed work, if we simply use the `ftok` generated 67 | key that deterministically transforms a file path (i.e. the domain socket path) 68 | to a key, suitable for shared memory segments. Thus, this would probably be the 69 | better approach. Again, in `accept` and `connect` we optionally create or 70 | retrieve the shared memory segment and set the server or client's bit. Then, in 71 | `read` and `write`, we would have to do atomic loads to see if the client/server 72 | has set their bit, and if so store that boolean into a non-atomic boolean for 73 | faster loads. This shared data structure could be placed into the `Connection` 74 | struct. 75 | -------------------------------------------------------------------------------- /scripts/run-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DYLD_INSERT_LIBRARIES=$PWD/../tssx/libtssx-client.dylib \ 4 | DYLD_FORCE_FLAT_NAMESPACE=1 ./experiment-client -c $1 -s $2 5 | -------------------------------------------------------------------------------- /scripts/run-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DYLD_INSERT_LIBRARIES=$PWD/../tssx/libtssx-server.dylib \ 4 | DYLD_FORCE_FLAT_NAMESPACE=1 ./experiment-server -c $1 -s $2 5 | -------------------------------------------------------------------------------- /scripts/run-try-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ${OSTYPE//[0-9.]/} = darwin ]; then 4 | DYLD_INSERT_LIBRARIES=$PWD/../source/tssx/libtssx-client.dylib \ 5 | DYLD_FORCE_FLAT_NAMESPACE=1 ./try-client 6 | else 7 | LD_PRELOAD=$PWD/../source/tssx/libtssx-client.so ./try-client $1 8 | fi 9 | -------------------------------------------------------------------------------- /scripts/run-try-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd ../source/tssx 4 | make 5 | cd - 6 | 7 | if [ ${OSTYPE//[0-9.]/} = darwin ]; then 8 | DYLD_INSERT_LIBRARIES=$PWD/../source/tssx/libtssx-server.dylib \ 9 | DYLD_FORCE_FLAT_NAMESPACE=1 ./try-server $1 10 | else 11 | LD_PRELOAD=$PWD/../source/tssx/libtssx-server.so ./try-server $1 12 | fi 13 | -------------------------------------------------------------------------------- /source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## SUBDIRECTORIES 3 | ########################################################### 4 | 5 | add_subdirectory(tssx) 6 | add_subdirectory(utility) 7 | add_subdirectory(experiment) 8 | -------------------------------------------------------------------------------- /source/experiment/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## TARGETS 3 | ########################################################### 4 | 5 | add_executable(experiment-client client.c) 6 | add_executable(experiment-server server.c) 7 | 8 | ########################################################### 9 | ## DEPENDENCIES 10 | ########################################################### 11 | 12 | target_link_libraries(experiment-client tssx-utility) 13 | target_link_libraries(experiment-server tssx-utility) 14 | -------------------------------------------------------------------------------- /source/experiment/client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "utility/common.h" 9 | #include "utility/sockets.h" 10 | 11 | #define SOCKET_PATH "/tmp/domain_socket" 12 | 13 | void cleanup(int socket, void* buffer) { 14 | close(socket); 15 | free(buffer); 16 | } 17 | 18 | #include 19 | clock_t t_now() { 20 | return __rdtsc(); 21 | } 22 | 23 | void communicate(int connection, Arguments* args) { 24 | void* buffer = malloc(args->size); 25 | 26 | // Benchmarks can start 27 | // client_once(NOTIFY); 28 | if (write(connection, buffer, 1) < 1) { 29 | throw("Error writing first byte to server"); 30 | } 31 | 32 | for (; args->count > 0; --args->count) { 33 | // Dummy operation 34 | memset(buffer, '*', args->size); 35 | 36 | // printf("Client start\n"); 37 | // clock_t start = t_now(); 38 | 39 | if (write(connection, buffer, args->size) != args->size) { 40 | throw("Error writing on client-side"); 41 | } 42 | 43 | // printf("Client write done %llu\n", t_now() - start); 44 | 45 | // start = t_now(); 46 | 47 | if (read(connection, buffer, args->size) != args->size) { 48 | throw("Error reading on client-side"); 49 | } 50 | 51 | // printf("Client read done %llu\n", t_now() - start); 52 | } 53 | 54 | cleanup(connection, buffer); 55 | } 56 | 57 | void setup_socket_address(struct sockaddr_un* address) { 58 | address->sun_family = AF_LOCAL; 59 | strcpy(address->sun_path, SOCKET_PATH); 60 | } 61 | 62 | int create_socket() { 63 | int connection; 64 | 65 | connection = socket(PF_LOCAL, SOCK_STREAM, 0); 66 | 67 | if (connection == -1) { 68 | throw("Error creating socket on client-side"); 69 | } 70 | 71 | set_socket_both_buffer_sizes(connection); 72 | 73 | return connection; 74 | } 75 | 76 | void connect_socket(int connection) { 77 | int return_code; 78 | struct sockaddr_un server_address; 79 | 80 | setup_socket_address(&server_address); 81 | 82 | // clang-format off 83 | return_code = connect( 84 | connection, 85 | (struct sockaddr*)&server_address, 86 | SUN_LEN(&server_address) 87 | ); 88 | // clang-format on 89 | 90 | if (return_code == -1) { 91 | throw("Error connecting to server"); 92 | } 93 | 94 | // set_socket_non_blocking(connection); 95 | } 96 | 97 | int connect_to_server() { 98 | // The connection socket 99 | int connection; 100 | 101 | // Wait until the server created the socket 102 | // client_once(WAIT); 103 | 104 | connection = create_socket(); 105 | connect_socket(connection); 106 | 107 | return connection; 108 | } 109 | 110 | int main(int argc, char* argv[]) { 111 | // The socket through which we communicate with the client 112 | int connection; 113 | 114 | Arguments args; 115 | parse_arguments(&args, argc, argv); 116 | 117 | connection = connect_to_server(); 118 | communicate(connection, &args); 119 | 120 | return EXIT_SUCCESS; 121 | } 122 | -------------------------------------------------------------------------------- /source/experiment/run-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | DYLD_INSERT_LIBRARIES=$PWD/../tssx/libtssx-client.dylib \ 4 | DYLD_FORCE_FLAT_NAMESPACE=1 ./experiment-client -c $1 -s $2 5 | -------------------------------------------------------------------------------- /source/experiment/run-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | DYLD_INSERT_LIBRARIES=$PWD/../tssx/libtssx-server.dylib \ 4 | DYLD_FORCE_FLAT_NAMESPACE=1 ./experiment-server -c $1 -s $2 5 | -------------------------------------------------------------------------------- /source/experiment/server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "utility/common.h" 10 | #include "utility/sockets.h" 11 | 12 | #define SOCKET_PATH "/tmp/domain_socket" 13 | 14 | void cleanup(int connection, void* buffer) { 15 | close(connection); 16 | free(buffer); 17 | if (remove(SOCKET_PATH) == -1) { 18 | throw("Error removing domain socket"); 19 | } 20 | } 21 | 22 | #include 23 | clock_t t_now() { 24 | return __rdtsc(); 25 | } 26 | 27 | void communicate(int connection, Arguments* args) { 28 | Benchmarks bench; 29 | int message; 30 | void* buffer; 31 | // clock_t start; 32 | 33 | buffer = malloc(args->size); 34 | setup_benchmarks(&bench); 35 | 36 | // Synchronize for the benchmarks 37 | // server_once(WAIT); 38 | if (read(connection, buffer, 1) < 1) { 39 | throw("Error reading first byte from client"); 40 | } 41 | 42 | for (message = 0; message < args->count; ++message) { 43 | bench.single_start = now(); 44 | 45 | // printf("Server start\n"); 46 | // clock_t start = t_now(); 47 | 48 | if (read(connection, buffer, args->size) != args->size) { 49 | throw("Error reading on server-side"); 50 | } 51 | 52 | // printf("Server read done %llu\n", t_now() - start); 53 | 54 | memset(buffer, '*', args->size); 55 | 56 | // start = t_now(); 57 | // printf("Server write done %llu\n", t_now() - start); 58 | // start = t_now(); 59 | if (write(connection, buffer, args->size) != args->size) { 60 | throw("Error sending on server-side"); 61 | } 62 | 63 | // printf("Server write done %llu\n", t_now() - start); 64 | 65 | benchmark(&bench); 66 | } 67 | 68 | evaluate(&bench, args); 69 | cleanup(connection, buffer); 70 | } 71 | 72 | void setup_socket_address(struct sockaddr_un* address) { 73 | address->sun_family = AF_UNIX; 74 | strcpy(address->sun_path, SOCKET_PATH); 75 | 76 | // Remove it if it already exists (will throw EINVAL 77 | // error if it already exists and we try to create it) 78 | unlink(address->sun_path); 79 | } 80 | 81 | void setup_socket(int server_socket) { 82 | int return_code; 83 | struct sockaddr_un address; 84 | 85 | setup_socket_address(&address); 86 | 87 | // clang-format off 88 | return_code = bind( 89 | server_socket, 90 | (struct sockaddr*) &address, 91 | SUN_LEN(&address) 92 | ); 93 | // clang-format on 94 | 95 | if (return_code == -1) { 96 | throw("Error binding socket to address!"); 97 | } 98 | 99 | // Start listening on the socket (max 10 queueing) 100 | return_code = listen(server_socket, 10); 101 | 102 | if (return_code == -1) { 103 | throw("Could not start listening on socket"); 104 | } 105 | } 106 | 107 | int create_server_socket() { 108 | // The socket on which the server receives 109 | // all incoming connections 110 | int server_socket; 111 | 112 | if ((server_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 113 | throw("Error opening server-socket on server-side"); 114 | } 115 | 116 | setup_socket(server_socket); 117 | 118 | // server_once(NOTIFY); 119 | 120 | return server_socket; 121 | } 122 | 123 | int accept_client(int server_socket) { 124 | int client_socket; 125 | struct sockaddr_un client_address; 126 | socklen_t length = sizeof client_socket; 127 | 128 | // clang-format off 129 | client_socket = accept( 130 | server_socket, 131 | (struct sockaddr*)&client_address, 132 | &length 133 | ); 134 | // clang-format on 135 | 136 | if (client_socket == -1) { 137 | throw("Error accepting connection"); 138 | } 139 | 140 | set_socket_both_buffer_sizes(client_socket); 141 | 142 | return client_socket; 143 | } 144 | 145 | int connect_to_client() { 146 | // The socket on which the server receives 147 | // all incoming connections 148 | int server_socket; 149 | // The socket for unique communication with the client 150 | int client_socket; 151 | 152 | server_socket = create_server_socket(); 153 | client_socket = accept_client(server_socket); 154 | 155 | // Don't need the server socket anymore (only have one connection) 156 | // close(client_socket); 157 | 158 | if (fork() != (pid_t)0) { 159 | // Don't need the server socket anymore (only have one connection) 160 | close(client_socket); 161 | close(server_socket); 162 | exit(EXIT_SUCCESS); 163 | } 164 | 165 | return client_socket; 166 | } 167 | 168 | int main(int argc, char* argv[]) { 169 | // The socket through which we communicate with the client 170 | int connection; 171 | 172 | Arguments args; 173 | parse_arguments(&args, argc, argv); 174 | 175 | connection = connect_to_client(); 176 | communicate(connection, &args); 177 | 178 | return EXIT_SUCCESS; 179 | } 180 | -------------------------------------------------------------------------------- /source/tssx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## SOURCES 3 | ########################################################### 4 | 5 | set(TSSX_COMMON_SOURCES 6 | ${CMAKE_CURRENT_SOURCE_DIR}/buffer.c 7 | ${CMAKE_CURRENT_SOURCE_DIR}/connection.c 8 | ${CMAKE_CURRENT_SOURCE_DIR}/connection-options.c 9 | ${CMAKE_CURRENT_SOURCE_DIR}/hashtable.c 10 | ${CMAKE_CURRENT_SOURCE_DIR}/common-overrides.c 11 | ${CMAKE_CURRENT_SOURCE_DIR}/socket-overrides.c 12 | ${CMAKE_CURRENT_SOURCE_DIR}/poll-overrides.c 13 | ${CMAKE_CURRENT_SOURCE_DIR}/select-overrides.c 14 | ${CMAKE_CURRENT_SOURCE_DIR}/shared-memory.c 15 | ${CMAKE_CURRENT_SOURCE_DIR}/timeouts.c 16 | ${CMAKE_CURRENT_SOURCE_DIR}/string-set.c 17 | ${CMAKE_CURRENT_SOURCE_DIR}/selective.c 18 | ${CMAKE_CURRENT_SOURCE_DIR}/session-table.c 19 | ${CMAKE_CURRENT_SOURCE_DIR}/session.c 20 | ${CMAKE_CURRENT_SOURCE_DIR}/free-list.c 21 | ${CMAKE_CURRENT_SOURCE_DIR}/bridge.c 22 | ${CMAKE_CURRENT_SOURCE_DIR}/reverse-map.c 23 | ${CMAKE_CURRENT_SOURCE_DIR}/common-poll-overrides.c 24 | ) 25 | 26 | if(NOT APPLE) 27 | set(TSSX_COMMON_SOURCES 28 | ${TSSX_COMMON_SOURCES} 29 | ${CMAKE_CURRENT_SOURCE_DIR}/epoll-overrides.c 30 | ) 31 | endif() 32 | 33 | set(TSSX_SERVER_SOURCES 34 | ${TSSX_COMMON_SOURCES} 35 | ${CMAKE_CURRENT_SOURCE_DIR}/server-overrides.c 36 | ) 37 | 38 | set(TSSX_CLIENT_SOURCES 39 | ${TSSX_COMMON_SOURCES} 40 | ${CMAKE_CURRENT_SOURCE_DIR}/client-overrides.c 41 | ) 42 | 43 | ########################################################### 44 | ## TARGETS 45 | ########################################################### 46 | 47 | add_library(tssx-server SHARED ${TSSX_SERVER_SOURCES}) 48 | add_library(tssx-client SHARED ${TSSX_CLIENT_SOURCES}) 49 | 50 | ########################################################### 51 | ## COMPILER FLAGS 52 | ########################################################### 53 | 54 | set(TSSX_DEPENDENCIES tssx-utility dl) 55 | 56 | target_link_libraries(tssx-server ${TSSX_DEPENDENCIES}) 57 | target_link_libraries(tssx-client ${TSSX_DEPENDENCIES}) 58 | -------------------------------------------------------------------------------- /source/tssx/bridge.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 500 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "tssx/bridge.h" 8 | #include "tssx/connection.h" 9 | #include "tssx/session.h" 10 | #include "utility/utility.h" 11 | 12 | 13 | /******************** GLOBAL DATA ********************/ 14 | 15 | Bridge bridge = BRIDGE_INITIALIZER; 16 | 17 | signal_handler_t old_sigint_handler = NULL; 18 | signal_handler_t old_sigterm_handler = NULL; 19 | signal_handler_t old_sigabrt_handler = NULL; 20 | 21 | /******************** INTERFACE ********************/ 22 | 23 | int bridge_setup(Bridge* bridge) { 24 | assert(!bridge_is_initialized(bridge)); 25 | 26 | session_table_setup(&bridge->session_table); 27 | _setup_exit_handling(); 28 | 29 | bridge->is_initialized = true; 30 | bridge->connection_count = 0; 31 | 32 | return SUCCESS; 33 | } 34 | 35 | int bridge_destroy(Bridge* bridge) { 36 | assert(bridge_is_initialized(bridge)); 37 | session_table_destroy(&bridge->session_table); 38 | return SUCCESS; 39 | } 40 | 41 | bool bridge_is_initialized(const Bridge* bridge) { 42 | assert(bridge != NULL); 43 | return bridge->is_initialized; 44 | } 45 | 46 | int bridge_add_user(Bridge* bridge) { 47 | if (_lazy_bridge_setup(bridge) == ERROR) return ERROR; 48 | if (bridge->connection_count == 0) return SUCCESS; 49 | 50 | for (size_t index = 0; index < SESSION_TABLE_SIZE; ++index) { 51 | Session* session = session_table_get(&bridge->session_table, index); 52 | if (session_has_connection(session)) { 53 | connection_add_user(session->connection); 54 | } 55 | } 56 | 57 | return SUCCESS; 58 | } 59 | 60 | int bridge_insert(Bridge* bridge, int fd, Session* session) { 61 | if (_lazy_bridge_setup(bridge) == ERROR) return ERROR; 62 | 63 | session_table_assign(&bridge->session_table, fd, session); 64 | 65 | if (session_has_connection(session)) { 66 | bridge->connection_count++; 67 | } 68 | 69 | return SUCCESS; 70 | } 71 | 72 | int bridge_erase(Bridge* bridge, int fd) { 73 | Session* session; 74 | 75 | if (_lazy_bridge_setup(bridge) == ERROR) return ERROR; 76 | 77 | session = session_table_get(&bridge->session_table, fd); 78 | 79 | if (session_has_connection(session)) { 80 | bridge->connection_count--; 81 | } 82 | 83 | session_invalidate(session); 84 | 85 | return SUCCESS; 86 | } 87 | 88 | Session* bridge_lookup(Bridge* bridge, int fd) { 89 | if (_lazy_bridge_setup(bridge) == ERROR) return NULL; 90 | return session_table_get(&bridge->session_table, fd); 91 | } 92 | 93 | bool bridge_has_connection(Bridge* bridge, int fd) { 94 | const Session* session; 95 | 96 | if (_lazy_bridge_setup(bridge) == ERROR) return ERROR; 97 | 98 | session = session_table_get(&bridge->session_table, fd); 99 | 100 | return session_has_connection(session); 101 | } 102 | 103 | bool bridge_has_any_connections(const Bridge* bridge) { 104 | return bridge->connection_count > 0; 105 | } 106 | 107 | /******************** PRIVATE ********************/ 108 | 109 | int _lazy_bridge_setup(Bridge* bridge) { 110 | if (!bridge_is_initialized(bridge)) { 111 | if (bridge_setup(bridge) == ERROR) { 112 | return ERROR; 113 | } 114 | } 115 | 116 | return SUCCESS; 117 | } 118 | 119 | void _setup_exit_handling() { 120 | _setup_signal_handler(SIGINT); 121 | _setup_signal_handler(SIGTERM); 122 | _setup_signal_handler(SIGABRT); 123 | 124 | // With atexit we can register up to 32 functions that are 125 | // called at *normal* program termination. *Normal* means 126 | // either return from main or a call to exit(). 127 | atexit(_bridge_exit_handler); 128 | } 129 | 130 | void _setup_signal_handler(int signal_number) { 131 | struct sigaction signal_action, old_action; 132 | 133 | // clang-format off 134 | assert(signal_number == SIGTERM || 135 | signal_number == SIGINT || 136 | signal_number == SIGABRT); 137 | // clang-format on 138 | 139 | // Set our function as the signal handling function 140 | signal_action.sa_handler = _bridge_signal_handler; 141 | 142 | // Don't block any other signals during our exception handler 143 | sigemptyset(&signal_action.sa_mask); 144 | 145 | // Attempt to restart syscalls after our signal handler 146 | // (useful only for non-terminating signals) 147 | signal_action.sa_flags = SA_RESTART; 148 | 149 | if (sigaction(signal_number, &signal_action, &old_action) == -1) { 150 | throw("Error setting signal handler in bridge"); 151 | } 152 | 153 | if (signal_number == SIGINT) { 154 | old_sigint_handler = old_action.sa_handler; 155 | } else if (signal_number == SIGTERM) { 156 | old_sigterm_handler = old_action.sa_handler; 157 | } else { 158 | old_sigabrt_handler = old_action.sa_handler; 159 | } 160 | } 161 | 162 | void _bridge_signal_handler(int signal_number) { 163 | if (signal_number == SIGINT) { 164 | _bridge_signal_handler_for(SIGINT, old_sigint_handler); 165 | } else if (signal_number == SIGTERM) { 166 | _bridge_signal_handler_for(SIGTERM, old_sigterm_handler); 167 | } else if (signal_number == SIGABRT) { 168 | _bridge_signal_handler_for(SIGABRT, old_sigabrt_handler); 169 | } 170 | } 171 | 172 | void _bridge_signal_handler_for(int signal_number, 173 | signal_handler_t old_handler) { 174 | // There are five cases interesting here: 175 | // 1) The user has no handler set. Then we decide to exit (and destroy the 176 | // bridge) by calling exit(). 177 | // 2) The user has a handler set and ignores the signal. Then we ignore it too 178 | // and everything can go on normally. 179 | // 3) The user has a handler set and calls exit() at the end. Then our bridge 180 | // will be destroyed via the function we registered with atexit() (all good). 181 | // 4) The signal is SIGABRT (abort) and the user has a handler set and exits 182 | // the program via exit(). Then our handler will be called and we're good. 183 | // 5) The signal is SIGABRT (abort) and the user has a handler set and returns 184 | // from his handler. According to the man pages, when a signal handler for 185 | // SIGABRT returns, it replaces the current handler with the default handler 186 | // (which does a core dump) and re-raises the SIGABRT signal. So when we 187 | // call the user's handler for SIGABRT and he returns, we kill the bridge 188 | // because we know (and hopefully the user knew) that the process will be 189 | // terminated anyway. 190 | 191 | // Note: PostGres seems to ignore SIGINT at one point. SQLite exit()s after 192 | // three SIGINTs are sent (maybe to prevent accidental ^C from killing it...) 193 | 194 | if (old_handler != NULL) { 195 | old_handler(signal_number); 196 | } else { 197 | exit(EXIT_FAILURE); 198 | } 199 | 200 | if (signal_number == SIGABRT) { 201 | bridge_destroy(&bridge); 202 | } 203 | } 204 | 205 | void _bridge_exit_handler() { 206 | bridge_destroy(&bridge); 207 | } 208 | -------------------------------------------------------------------------------- /source/tssx/buffer.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 500 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef DEBUG 12 | #include 13 | #endif 14 | 15 | #include "tssx/buffer.h" 16 | #include "tssx/definitions.h" 17 | #include "tssx/timeouts.h" 18 | #include "utility/utility.h" 19 | 20 | Buffer *create_buffer(void *shared_memory, 21 | size_t requested_capacity, 22 | const Timeouts *timeouts) { 23 | Buffer *buffer = (Buffer *)shared_memory; 24 | 25 | assert(requested_capacity > 0); 26 | 27 | buffer->capacity = requested_capacity; 28 | buffer->timeouts = *timeouts; 29 | buffer_clear(buffer); 30 | 31 | return buffer; 32 | } 33 | 34 | size_t buffer_write(Buffer *buffer, const void *data, size_t bytes_to_write) { 35 | size_t right_space = 0; 36 | 37 | if (buffer == NULL) return ERROR; 38 | if (data == NULL) return ERROR; 39 | if (bytes_to_write == 0) return 0; 40 | 41 | // Block or return, depending on blocking configuration for the socket 42 | // After the branch there is enough space to do the operation 43 | size_t available_space = _determine_available_space(buffer, WRITE); 44 | bytes_to_write = 45 | available_space < bytes_to_write ? available_space : bytes_to_write; 46 | 47 | // The == is when the buffer is empty 48 | if (buffer->write >= buffer->read) { 49 | // Available space to the right of the write pointer 50 | right_space = buffer->capacity - buffer->write; 51 | 52 | if (bytes_to_write >= right_space) { 53 | // Write first portion, up to the end of the buffer 54 | memcpy(_write_pointer(buffer), data, right_space); 55 | _wrap_write(buffer, &data, &bytes_to_write, right_space); 56 | } else { 57 | right_space = 0; 58 | } 59 | } 60 | 61 | memcpy(_write_pointer(buffer), data, bytes_to_write); 62 | 63 | buffer->write += bytes_to_write; 64 | atomic_fetch_add(&buffer->size, bytes_to_write); 65 | 66 | // How many bytes we wrote 67 | return bytes_to_write + right_space; 68 | } 69 | 70 | size_t buffer_read(Buffer *buffer, void *data, size_t bytes_to_read) { 71 | size_t right_space = 0; 72 | 73 | if (buffer == NULL) return ERROR; 74 | if (data == NULL) return ERROR; 75 | if (bytes_to_read == 0) return 0; 76 | 77 | // Block or return, depending on blocking configuration for the socket 78 | // After the branch there is enough space to do the operation 79 | size_t available_space = _determine_available_space(buffer, READ); 80 | bytes_to_read = 81 | available_space < bytes_to_read ? available_space : bytes_to_read; 82 | 83 | // Read bytes_to_read from buffer 84 | if (buffer->read >= buffer->write) { 85 | right_space = buffer->capacity - buffer->read; 86 | 87 | if (bytes_to_read >= right_space) { 88 | // Read first portion, then wrap around and write the rest below 89 | memcpy(data, _read_pointer(buffer), right_space); 90 | _wrap_read(buffer, &data, &bytes_to_read, right_space); 91 | } else { 92 | right_space = 0; 93 | } 94 | } 95 | 96 | memcpy(data, _read_pointer(buffer), bytes_to_read); 97 | 98 | buffer->read += bytes_to_read; 99 | atomic_fetch_sub(&buffer->size, bytes_to_read); 100 | 101 | // How many bytes we read (Needs to be -1 if nothing was read) 102 | return (bytes_to_read + right_space) == 0 ? (size_t)-1 103 | : (bytes_to_read + right_space); 104 | } 105 | 106 | size_t buffer_peek(Buffer *buffer, void *data, size_t data_size) { 107 | size_t return_value; 108 | size_t old_size; 109 | size_t old_read; 110 | 111 | old_size = atomic_load(&buffer->size); 112 | old_read = buffer->read; 113 | 114 | return_value = buffer_read(buffer, data, data_size); 115 | 116 | // Restore 117 | atomic_store(&buffer->size, old_size); 118 | buffer->read = old_read; 119 | 120 | return return_value; 121 | } 122 | 123 | size_t buffer_skip(Buffer *buffer, size_t number_of_bytes) { 124 | assert(buffer != NULL); 125 | 126 | if (number_of_bytes > atomic_load(&buffer->size)) return ERROR; 127 | 128 | buffer->read = (buffer->read + number_of_bytes) % buffer->capacity; 129 | 130 | return number_of_bytes; 131 | } 132 | 133 | void buffer_clear(Buffer *buffer) { 134 | buffer->read = 0; 135 | buffer->write = 0; 136 | atomic_store(&buffer->size, 0); 137 | } 138 | 139 | bool buffer_is_full(Buffer *buffer) { 140 | return atomic_load(&buffer->size) == buffer->capacity; 141 | } 142 | 143 | bool buffer_is_empty(Buffer *buffer) { 144 | return atomic_load(&buffer->size) == 0; 145 | } 146 | 147 | bool buffer_ready_for(Buffer *buffer, Operation operation) { 148 | if (operation == READ) { 149 | return !buffer_is_empty(buffer); 150 | } else { 151 | return !buffer_is_full(buffer); 152 | } 153 | } 154 | 155 | #ifdef TSSX_SUPPORT_BUFFER_TIMEOUTS 156 | void buffer_set_timeout(Buffer *buffer, 157 | Operation operation, 158 | cycle_t new_timeout) { 159 | buffer->timeouts.timeout[operation] = new_timeout; 160 | } 161 | 162 | bool buffer_has_timeout(Buffer *buffer, Operation operation) { 163 | return buffer->timeouts.timeout[operation] != 0; 164 | } 165 | #endif 166 | 167 | size_t buffer_free_space(Buffer *buffer) { 168 | return buffer->capacity - atomic_load(&buffer->size); 169 | } 170 | 171 | /******* PRIVATE *******/ 172 | 173 | void *_start_pointer(Buffer *buffer) { 174 | // The start of the shared memory data segment is 175 | // at (void*)&buffer + sizeof(buffer), i.e. buffer + 1 176 | return (void *)++buffer; 177 | } 178 | 179 | void *_end_pointer(Buffer *buffer) { 180 | return _pointer_to(buffer, buffer->capacity); 181 | } 182 | 183 | void *_read_pointer(Buffer *buffer) { 184 | return _pointer_to(buffer, buffer->read); 185 | } 186 | 187 | void *_write_pointer(Buffer *buffer) { 188 | return _pointer_to(buffer, buffer->write); 189 | } 190 | 191 | void *_pointer_to(Buffer *buffer, size_t index) { 192 | assert(index <= buffer->capacity); 193 | 194 | return _start_pointer(buffer) + index; 195 | } 196 | 197 | ptrdiff_t _index_at(Buffer *buffer, void *pointer) { 198 | assert(pointer >= _start_pointer(buffer)); 199 | assert(pointer <= _end_pointer(buffer)); 200 | 201 | return pointer - _start_pointer(buffer); 202 | } 203 | 204 | void _wrap_read(Buffer *buffer, void **data, size_t *data_size, size_t delta) { 205 | buffer->read = 0; 206 | atomic_fetch_sub(&buffer->size, delta); 207 | _reduce_data((const void **)data, 208 | data_size, 209 | delta);// TODO: Is this cast ok ??? 210 | } 211 | 212 | void _wrap_write(Buffer *buffer, 213 | const void **data, 214 | size_t *data_size, 215 | size_t delta) { 216 | buffer->write = 0; 217 | atomic_fetch_add(&buffer->size, delta); 218 | _reduce_data(data, data_size, delta); 219 | } 220 | 221 | void _reduce_data(const void **data, size_t *data_size, size_t delta) { 222 | *data_size -= delta; 223 | *data += delta; 224 | } 225 | 226 | #ifdef TSSX_SUPPORT_BUFFER_TIMEOUTS 227 | bool _timeout_elapsed(Buffer *buffer, cycle_t elapsed, Operation operation) { 228 | if (!buffer_has_timeout(buffer, operation)) return false; 229 | return elapsed > buffer->timeouts.timeout[operation]; 230 | } 231 | #endif 232 | 233 | bool _level_elapsed(Buffer *buffer, size_t level, cycle_t elapsed) { 234 | return elapsed > buffer->timeouts.levels[level]; 235 | } 236 | 237 | void _pause() { 238 | _mm_pause(); 239 | } 240 | 241 | cycle_t _now() { 242 | return __rdtsc(); 243 | } 244 | 245 | int _escalation_level(Buffer *buffer, cycle_t start_time, Operation operation) { 246 | cycle_t elapsed = _now() - start_time; 247 | 248 | #ifdef TSSX_SUPPORT_BUFFER_TIMEOUTS 249 | if (_timeout_elapsed(buffer, elapsed, operation)) { 250 | errno = EWOULDBLOCK; 251 | return TIMEOUT; 252 | } 253 | #endif 254 | 255 | if (!_level_elapsed(buffer, LEVEL_ZERO, elapsed)) { 256 | return LEVEL_ZERO; 257 | } else if (!_level_elapsed(buffer, LEVEL_ONE, elapsed)) { 258 | return LEVEL_ONE; 259 | } else { 260 | return LEVEL_TWO; 261 | } 262 | } 263 | 264 | size_t _determine_available_space(Buffer *buffer, Operation operation) { 265 | size_t available_space; 266 | if (buffer->timeouts.non_blocking[operation]) { 267 | available_space = _get_available_space(buffer, operation); 268 | } else { 269 | available_space = _block_for_available_space(buffer, operation); 270 | } 271 | if (available_space == 0) { 272 | return 0; 273 | } 274 | 275 | return available_space; 276 | } 277 | 278 | size_t _get_available_space(Buffer *buffer, Operation operation) { 279 | if (operation == READ) { 280 | return atomic_load(&buffer->size); 281 | } else { 282 | return buffer_free_space(buffer); 283 | } 284 | } 285 | 286 | size_t _block_for_available_space(Buffer *buffer, Operation operation) { 287 | cycle_t start_time; 288 | 289 | start_time = _now(); 290 | size_t space = _get_available_space(buffer, operation); 291 | while (space == 0) { 292 | switch (_escalation_level(buffer, start_time, operation)) { 293 | case LEVEL_ZERO: _pause(); break; 294 | case LEVEL_ONE: sched_yield(); break; 295 | case LEVEL_TWO: usleep(1); break; 296 | #ifdef TSSX_SUPPORT_BUFFER_TIMEOUTS 297 | case TIMEOUT: print_error("not impl"); return TIMEOUT; 298 | #endif 299 | } 300 | space = _get_available_space(buffer, operation); 301 | } 302 | 303 | return space; 304 | } 305 | -------------------------------------------------------------------------------- /source/tssx/client-overrides.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "tssx/client-overrides.h" 8 | #include "tssx/common-overrides.h" 9 | #include "utility/sockets.h" 10 | 11 | int connect(int fd, const sockaddr* address, socklen_t length) { 12 | int use_tssx; 13 | 14 | if (real_connect(fd, address, length) == ERROR) { 15 | return ERROR; 16 | } 17 | 18 | if ((use_tssx = check_tssx_usage(fd, CLIENT)) == ERROR) { 19 | print_error("Could not check if socket uses TSSX"); 20 | return ERROR; 21 | } else if (!use_tssx) { 22 | return SUCCESS; 23 | } 24 | 25 | return _setup_tssx(fd); 26 | } 27 | 28 | ssize_t read(int fd, void* destination, size_t requested_bytes) { 29 | // clang-format off 30 | return connection_read( 31 | fd, 32 | destination, 33 | requested_bytes, 34 | SERVER_BUFFER 35 | ); 36 | // clang-format on 37 | } 38 | 39 | ssize_t write(int fd, const void* source, size_t requested_bytes) { 40 | // clang-format off 41 | return connection_write( 42 | fd, 43 | source, 44 | requested_bytes, 45 | CLIENT_BUFFER 46 | ); 47 | // clang-format on 48 | } 49 | 50 | /******************** HELPERS ********************/ 51 | 52 | int _setup_tssx(int fd) { 53 | int segment_id; 54 | Session session; 55 | ConnectionOptions options; 56 | 57 | // Read the options first 58 | options = options_from_socket(fd, CLIENT); 59 | 60 | segment_id = _read_segment_id_from_server(fd); 61 | if (segment_id == ERROR) { 62 | return ERROR; 63 | } 64 | 65 | session.connection = setup_connection(segment_id, &options); 66 | if (session.connection == NULL) { 67 | return ERROR; 68 | } 69 | 70 | if (_synchronize_with_server(fd) == ERROR) { 71 | print_error("Error synchronizing with server during setup\n"); 72 | return ERROR; 73 | } 74 | 75 | return bridge_insert(&bridge, fd, &session); 76 | } 77 | 78 | int _read_segment_id_from_server(int client_socket) { 79 | int return_code; 80 | int segment_id; 81 | int flags; 82 | 83 | // Get the old flags and unset the non-blocking flag (if set) 84 | flags = unset_socket_non_blocking(client_socket); 85 | 86 | // clang-format off 87 | return_code = real_read( 88 | client_socket, 89 | &segment_id, 90 | sizeof segment_id 91 | ); 92 | // clang-format on 93 | 94 | // Put the old flags back in place 95 | // set_socket_flags(client_socket, flags); 96 | 97 | // We do need to keep the socket open though, so that 98 | // its descriptor cannot be reused by the operating system 99 | 100 | if (return_code == ERROR) { 101 | print_error("Error receiving segment ID on client side"); 102 | return ERROR; 103 | } 104 | 105 | return segment_id; 106 | } 107 | 108 | int _synchronize_with_server(int fd) { 109 | return (real_write(fd, "!", 1) == ERROR) ? ERROR : SUCCESS; 110 | } 111 | 112 | /******************** "POLYMORPHIC" FUNCTIONS ********************/ 113 | 114 | void set_non_blocking(Connection* connection, bool non_blocking) { 115 | connection->server_buffer->timeouts.non_blocking[READ] = non_blocking; 116 | connection->client_buffer->timeouts.non_blocking[WRITE] = non_blocking; 117 | } 118 | 119 | bool is_non_blocking(Connection* connection) { 120 | assert(connection->server_buffer->timeouts.non_blocking[READ] == 121 | connection->client_buffer->timeouts.non_blocking[WRITE]); 122 | return connection->client_buffer->timeouts.non_blocking[WRITE]; 123 | } 124 | 125 | bool _ready_for(Connection* connection, Operation operation) { 126 | assert(connection != NULL); 127 | if (operation == READ) { 128 | return buffer_ready_for(connection->server_buffer, READ); 129 | } else { 130 | return buffer_ready_for(connection->client_buffer, WRITE); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /source/tssx/common-overrides.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "tssx/bridge.h" 10 | #include "tssx/buffer.h" 11 | #include "tssx/common-overrides.h" 12 | 13 | /******************** REAL FUNCTIONS ********************/ 14 | 15 | int real_fcntl_set_flags(int fd, int command, int flag) { 16 | return ((real_fcntl_t)dlsym(RTLD_NEXT, "fcntl"))(fd, command, flag); 17 | } 18 | 19 | int real_fcntl_get_flags(int fd, int command) { 20 | return ((real_fcntl_t)dlsym(RTLD_NEXT, "fcntl"))(fd, command); 21 | } 22 | 23 | pid_t real_fork(void) { 24 | return ((real_fork_t)dlsym(RTLD_NEXT, "fork"))(); 25 | } 26 | 27 | /******************** COMMON OVERRIDES ********************/ 28 | 29 | int fcntl(int fd, int command, ...) { 30 | va_list argument; 31 | 32 | // Takes the argument pointer and the last positional argument 33 | // Makes the argument pointer point to the first optional argument 34 | va_start(argument, command); 35 | 36 | if (command == F_SETFL || command == F_SETFD) { 37 | return fcntl_set(fd, command, va_arg(argument, int)); 38 | } else if (command == F_GETFL || command == F_GETFD) { 39 | return fcntl_get(fd, command); 40 | } else { 41 | // Sorry, don't know what to do for other commands :( 42 | // If necessary: handle all cases of arguments ... 43 | return ERROR; 44 | } 45 | } 46 | 47 | pid_t fork() { 48 | bridge_add_user(&bridge); 49 | return real_fork(); 50 | } 51 | 52 | /******************** INTERFACE ********************/ 53 | 54 | ssize_t connection_write(int key, 55 | const void* source, 56 | size_t requested_bytes, 57 | int which_buffer) { 58 | Session* session; 59 | 60 | session = bridge_lookup(&bridge, key); 61 | if (session_has_connection(session)) { 62 | if (connection_peer_died(session->connection)) return 0; 63 | // clang-format off 64 | return buffer_write( 65 | get_buffer(session->connection, which_buffer), 66 | source, 67 | requested_bytes 68 | ); 69 | // clang-format on 70 | } else { 71 | return real_write(key, source, requested_bytes); 72 | } 73 | } 74 | 75 | ssize_t connection_read(int key, 76 | void* destination, 77 | size_t requested_bytes, 78 | int which_buffer) { 79 | Session* session; 80 | session = bridge_lookup(&bridge, key); 81 | if (session_has_connection(session)) { 82 | // The problem is that the server is too quick to read and the client hasn't 83 | // incremented the count yet. so maybe the server should set the count to 2 84 | 85 | 86 | if (connection_peer_died(session->connection)) return 0; 87 | // clang-format off 88 | return buffer_read( 89 | get_buffer(session->connection, which_buffer), 90 | destination, 91 | requested_bytes 92 | ); 93 | // clang-format on 94 | } else { 95 | return real_read(key, destination, requested_bytes); 96 | } 97 | } 98 | 99 | int socket_is_stream_and_domain(int domain, int type) { 100 | /* 101 | * The only point of this function is that we are allowed to include 102 | * here and access the AF_LOCAL symbolic name, while we're not 103 | * allowed in the server/client-overrides file. We could just hardcode the 104 | * constant for AF_LOCAL in those files, but that wouldn't be sustainable. 105 | * Actually, we are allowed, it's just a mess to replicate the prototypes of 106 | * the overwritten functions exactly (especially since they're sometimes 107 | * annotated with OS X extensions ...) 108 | * 109 | * Note that we'll only want to use tssx for stream (TCP-like) oriented 110 | * sockets, not datagram (UDP-like) sockets 111 | */ 112 | return domain == AF_LOCAL && type == SOCK_STREAM; 113 | } 114 | 115 | /******************** HELPERS ********************/ 116 | 117 | Buffer* get_buffer(Connection* connection, int which_buffer) { 118 | return which_buffer ? connection->client_buffer : connection->server_buffer; 119 | } 120 | 121 | int fcntl_set(int fd, int command, int flags) { 122 | Session* session; 123 | int return_code; 124 | 125 | // Always set it on the socket (we can try to keep the flags in sync) 126 | if ((return_code = real_fcntl_set_flags(fd, command, flags)) == ERROR) { 127 | return ERROR; 128 | } 129 | 130 | session = bridge_lookup(&bridge, fd); 131 | if (session_has_connection(session)) { 132 | // Polymorphic call (implemented by server/client in their overrides) 133 | set_non_blocking(session->connection, flags & O_NONBLOCK); 134 | } 135 | 136 | return SUCCESS; 137 | } 138 | 139 | int fcntl_get(int fd, int command) { 140 | Session* session; 141 | int flags; 142 | 143 | flags = real_fcntl_get_flags(fd, command); 144 | 145 | // Theoretically the flags should be in sync 146 | // But we will get the non-blocking property, just in case 147 | session = bridge_lookup(&bridge, fd); 148 | if (session_has_connection(session)) { 149 | // First unset the flag, then check if we have it set 150 | flags &= ~O_NONBLOCK; 151 | if (is_non_blocking(session->connection)) { 152 | flags |= O_NONBLOCK; 153 | } 154 | } 155 | 156 | return flags; 157 | } 158 | -------------------------------------------------------------------------------- /source/tssx/common-poll-overrides.c: -------------------------------------------------------------------------------- 1 | 2 | #define _GNU_SOURCE 3 | 4 | #include 5 | 6 | #include "tssx/common-poll-overrides.h" 7 | #include "tssx/connection.h" 8 | #include "utility/utility.h" 9 | 10 | bool _there_was_an_error(event_count_t* event_count) { 11 | return atomic_load(event_count) == ERROR; 12 | } 13 | 14 | 15 | bool _poll_timeout_elapsed(size_t start, int timeout) { 16 | if (timeout == BLOCK_FOREVER) return false; 17 | if (timeout == DONT_BLOCK) return true; 18 | 19 | return (current_milliseconds() - start) > timeout; 20 | } 21 | 22 | int _install_poll_signal_handler(struct sigaction* old_action) { 23 | struct sigaction signal_action; 24 | 25 | // Set our function as the signal handling function 26 | signal_action.sa_handler = _poll_signal_handler; 27 | 28 | // !!!!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!!!!!!!!!!!!! 29 | // Do not set SA_RESTART! This is precisely the point of all of this! 30 | // By NOT (NOT NOT NOT) setting SA_RESTART, any interrupted syscall will 31 | // NOT (NOT NOT NOT) restart automatically. Rather, it will fail with exit 32 | // code EINTR, which is precisely what we want. 33 | signal_action.sa_flags = 0; 34 | 35 | // Don't block any other signals during our exception handling 36 | sigemptyset(&signal_action.sa_mask); 37 | 38 | if (sigaction(POLL_SIGNAL, &signal_action, old_action) == ERROR) { 39 | print_error("Error setting signal handler for poll"); 40 | return ERROR; 41 | } 42 | 43 | return SUCCESS; 44 | } 45 | 46 | int _restore_old_signal_action(struct sigaction* old_action) { 47 | if (sigaction(POLL_SIGNAL, old_action, NULL) == ERROR) { 48 | print_error("Error restoring old signal handler for poll"); 49 | return ERROR; 50 | } 51 | 52 | return SUCCESS; 53 | } 54 | 55 | void _poll_signal_handler(int signal_number) { 56 | assert(signal_number == POLL_SIGNAL); 57 | } 58 | 59 | void _kill_normal_thread(pthread_t normal_thread) { 60 | // Send our POLL_SIGNAL to the thread doing real_poll() 61 | // This will terminate that thread, avoiding weird edge cases 62 | // where the normal thread would block indefinitely if it detects 63 | // no changes (e.g. on a single fd), even if there were many events 64 | // on the TSSX buffer in the main thread. Note: signals are a actually a 65 | // process-wide concept. But because we installed a signal handler, what 66 | // we can do is have the signal handler be invoked in the *normal_thread* 67 | // argument. If the disposition of the signal were a default (i.e. if we 68 | // had installed no signal handler) one, such as TERMINATE for SIGQUIT, 69 | // then that signal would be delivered to all threads, because all threads 70 | // run in the same process 71 | if (pthread_kill(normal_thread, POLL_SIGNAL) != SUCCESS) { 72 | throw("Error killing normal thread"); 73 | } 74 | } 75 | 76 | int _set_poll_mask(pthread_mutex_t* lock, 77 | const sigset_t* sigmask, 78 | sigset_t* original_mask) { 79 | if (pthread_mutex_lock(lock) != SUCCESS) { 80 | return ERROR; 81 | } 82 | 83 | if (pthread_sigmask(SIG_SETMASK, sigmask, original_mask) != SUCCESS) { 84 | return ERROR; 85 | } 86 | 87 | return SUCCESS; 88 | } 89 | 90 | int _restore_poll_mask(pthread_mutex_t* lock, const sigset_t* original_mask) { 91 | if (pthread_sigmask(SIG_SETMASK, original_mask, NULL) != SUCCESS) { 92 | return ERROR; 93 | } 94 | 95 | if (pthread_mutex_unlock(lock) != SUCCESS) { 96 | return ERROR; 97 | } 98 | 99 | return SUCCESS; 100 | } 101 | -------------------------------------------------------------------------------- /source/tssx/connection-options.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | #include 5 | 6 | 7 | #include "tssx/buffer.h" 8 | #include "tssx/connection-options.h" 9 | #include "tssx/connection.h" 10 | #include "utility/sockets.h" 11 | 12 | const ConnectionOptions DEFAULT_OPTIONS = 13 | DEFAULT_CONNECTION_OPTIONS_INITIALIZER; 14 | 15 | ConnectionOptions options_from_socket(int socket_fd, Side side) { 16 | ConnectionOptions options = DEFAULT_CONNECTION_OPTIONS_INITIALIZER; 17 | bool non_blocking; 18 | int server_action; 19 | int client_action; 20 | 21 | // Not using the socket buffer size because it's way too small (16 KB) 22 | options.server_buffer_size = DEFAULT_BUFFER_SIZE; 23 | options.client_buffer_size = DEFAULT_BUFFER_SIZE; 24 | 25 | non_blocking = socket_is_non_blocking(socket_fd); 26 | server_action = (side == SERVER) ? WRITE : READ; 27 | client_action = (side == CLIENT) ? WRITE : READ; 28 | 29 | options.server_timeouts.non_blocking[server_action] = non_blocking; 30 | options.client_timeouts.non_blocking[client_action] = non_blocking; 31 | 32 | #ifdef TSSX_SUPPORT_BUFFER_TIMEOUTS 33 | // clang-format off 34 | options.server_timeouts.timeout[server_action] = timeout_clocks( 35 | socket_fd, 36 | server_action 37 | ); 38 | options.client_timeouts.timeout[client_action] = timeout_clocks( 39 | socket_fd, 40 | client_action 41 | ); 42 | // clang-format on 43 | #endif 44 | 45 | return options; 46 | } 47 | 48 | size_t options_segment_size(const ConnectionOptions* options) { 49 | size_t segment_size = 0; 50 | 51 | segment_size += sizeof(atomic_count_t); 52 | segment_size += sizeof(Buffer) + options->server_buffer_size; 53 | segment_size += sizeof(Buffer) + options->client_buffer_size; 54 | 55 | return segment_size; 56 | } 57 | 58 | cycle_t timeout_clocks(int socket_fd, Direction direction) { 59 | double timeout_seconds = socket_timeout_seconds(socket_fd, direction); 60 | return SECONDS_TO_CLOCKS(timeout_seconds); 61 | } 62 | -------------------------------------------------------------------------------- /source/tssx/connection.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "tssx/buffer.h" 8 | #include "tssx/connection-options.h" 9 | #include "tssx/connection.h" 10 | #include "tssx/hashtable.h" 11 | #include "tssx/shared-memory.h" 12 | #include "utility/sockets.h" 13 | #include "utility/utility.h" 14 | 15 | /*************** PUBLIC **************/ 16 | 17 | Connection* create_connection(const ConnectionOptions* options) { 18 | Connection* connection; 19 | void* shared_memory; 20 | 21 | assert(options != NULL); 22 | 23 | if ((connection = malloc(sizeof *connection)) == NULL) { 24 | print_error("Error allocating memory for connection"); 25 | return NULL; 26 | } 27 | 28 | connection->segment_id = create_segment(options_segment_size(options)); 29 | shared_memory = attach_segment(connection->segment_id); 30 | 31 | _init_open_count(connection, shared_memory); 32 | _create_server_buffer(connection, shared_memory, options); 33 | _create_client_buffer(connection, shared_memory, options); 34 | 35 | return connection; 36 | } 37 | 38 | Connection* setup_connection(int segment_id, const ConnectionOptions* options) { 39 | Connection* connection; 40 | void* shared_memory; 41 | 42 | assert(options != NULL); 43 | 44 | if ((connection = malloc(sizeof *connection)) == NULL) { 45 | print_error("Error allocating memory for connection"); 46 | return NULL; 47 | } 48 | 49 | connection->segment_id = segment_id; 50 | shared_memory = attach_segment(connection->segment_id); 51 | 52 | _init_and_increment_open_count(connection, shared_memory); 53 | _retrieve_server_buffer(connection, shared_memory); 54 | _retrieve_client_buffer(connection, shared_memory); 55 | 56 | return connection; 57 | } 58 | 59 | void connection_add_user(Connection* connection) { 60 | assert(connection != NULL); 61 | atomic_fetch_add(connection->open_count, 1); 62 | } 63 | 64 | bool connection_peer_died(Connection* connection) { 65 | assert(connection != NULL); 66 | return atomic_load(connection->open_count) == 1; 67 | } 68 | 69 | void disconnect(Connection* connection) { 70 | assert(connection != NULL); 71 | 72 | atomic_fetch_sub(connection->open_count, 1); 73 | 74 | if (atomic_load(connection->open_count) == 0) { 75 | _destroy_connection(connection); 76 | } else { 77 | _detach_connection(connection); 78 | } 79 | } 80 | 81 | /*************** UTILITY **************/ 82 | 83 | void _create_server_buffer(Connection* connection, 84 | void* shared_memory, 85 | const ConnectionOptions* options) { 86 | shared_memory += sizeof(connection->open_count); 87 | // clang-format off 88 | connection->server_buffer = create_buffer( 89 | shared_memory, 90 | options->server_buffer_size, 91 | &options->server_timeouts 92 | ); 93 | // clang-format on 94 | } 95 | 96 | void _create_client_buffer(Connection* connection, 97 | void* shared_memory, 98 | const ConnectionOptions* options) { 99 | shared_memory = _client_buffer_offset(connection, shared_memory); 100 | // clang-format off 101 | connection->client_buffer = create_buffer( 102 | shared_memory, 103 | options->client_buffer_size, 104 | &options->client_timeouts 105 | ); 106 | // clang-format on 107 | } 108 | 109 | void _retrieve_server_buffer(Connection* connection, void* shared_memory) { 110 | shared_memory += sizeof(connection->open_count); 111 | connection->server_buffer = shared_memory; 112 | } 113 | 114 | void _retrieve_client_buffer(Connection* connection, void* shared_memory) { 115 | connection->client_buffer = _client_buffer_offset(connection, shared_memory); 116 | } 117 | 118 | void _init_open_count(Connection* connection, void* shared_memory) { 119 | connection->open_count = (atomic_count_t*)shared_memory; 120 | *(connection->open_count) = ATOMIC_VAR_INIT(1); 121 | } 122 | 123 | void _init_and_increment_open_count(Connection* connection, 124 | void* shared_memory) { 125 | connection->open_count = (atomic_count_t*)shared_memory; 126 | atomic_fetch_add(connection->open_count, 1); 127 | } 128 | 129 | void _detach_connection(Connection* connection) { 130 | detach_segment(_segment_start(connection)); 131 | 132 | // Invalidate 133 | connection->segment_id = -1; 134 | connection->open_count = NULL; 135 | connection->server_buffer = NULL; 136 | connection->client_buffer = NULL; 137 | } 138 | 139 | void _destroy_connection(Connection* connection) { 140 | // Must grab this first before detaching 141 | int segment_id = connection->segment_id; 142 | 143 | _detach_connection(connection); 144 | destroy_segment(segment_id); 145 | } 146 | 147 | void* _segment_start(Connection* connection) { 148 | return (void*)connection->open_count; 149 | } 150 | 151 | int _connection_segment_size(Connection* connection) { 152 | int segment_size = 0; 153 | 154 | segment_size += sizeof(atomic_count_t); 155 | segment_size += sizeof(Buffer) + connection->server_buffer->capacity; 156 | segment_size += sizeof(Buffer) + connection->client_buffer->capacity; 157 | 158 | return segment_size; 159 | } 160 | 161 | void* _client_buffer_offset(Connection* connection, void* shared_memory) { 162 | shared_memory += sizeof(connection->open_count); 163 | shared_memory += segment_size(connection->server_buffer); 164 | 165 | return shared_memory; 166 | } 167 | -------------------------------------------------------------------------------- /source/tssx/free-list.c: -------------------------------------------------------------------------------- 1 | #include "tssx/free-list.h" 2 | #include "tssx/bridge.h" 3 | #include "tssx/vector.h" 4 | #include "utility/utility.h" 5 | 6 | void free_list_setup(FreeList* list) { 7 | if (vector_setup(list, 0, sizeof(key_t)) == VECTOR_ERROR) { 8 | terminate("Error setting up free-list\n"); 9 | } 10 | } 11 | 12 | void free_list_destroy(FreeList* list) { 13 | if (vector_destroy(list) == VECTOR_ERROR) { 14 | terminate("Error destroying up free-list\n"); 15 | } 16 | } 17 | 18 | void free_list_push(FreeList* list, key_t key) { 19 | if (vector_push_back(list, &key) == VECTOR_ERROR) { 20 | terminate("Error pushing into free-list\n"); 21 | } 22 | } 23 | 24 | key_t free_list_pop(FreeList* list) { 25 | key_t* pointer; 26 | 27 | if ((pointer = (key_t*)vector_back(list)) == NULL) { 28 | terminate("Error retrieving back of free-list\n"); 29 | } 30 | 31 | if (vector_pop_back(list) == VECTOR_ERROR) { 32 | terminate("Error popping back of free-list\n"); 33 | } 34 | 35 | return *pointer; 36 | } 37 | 38 | bool free_list_is_empty(FreeList* list) { 39 | return vector_is_empty(list); 40 | } 41 | -------------------------------------------------------------------------------- /source/tssx/hashtable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "tssx/hashtable.h" 6 | 7 | void ht_setup(HashTable* table, size_t capacity) { 8 | assert(table != NULL); 9 | 10 | if (capacity < HT_MINIMUM_CAPACITY) { 11 | capacity = HT_MINIMUM_CAPACITY; 12 | } 13 | 14 | _ht_allocate(table, capacity); 15 | table->size = 0; 16 | } 17 | 18 | void ht_destroy(HashTable* table) { 19 | Node* node; 20 | Node* next; 21 | 22 | assert(table != NULL); 23 | 24 | for (size_t i = 0; i < table->capacity; ++i) { 25 | node = table->nodes[i]; 26 | while (node) { 27 | next = node->next; 28 | free(node); 29 | node = next; 30 | } 31 | } 32 | 33 | free(table->nodes); 34 | } 35 | 36 | bool ht_insert(HashTable* table, int key, Connection* connection) { 37 | Node* node; 38 | size_t index; 39 | 40 | _ht_create_if_necessary(table); 41 | 42 | if (table->size == table->threshold) { 43 | _ht_resize(table); 44 | } 45 | 46 | index = _ht_hash(table, key); 47 | node = table->nodes[index]; 48 | 49 | for (; node; node = node->next) { 50 | if (node->key == key) { 51 | node->connection = *connection; 52 | return HT_UPDATED; 53 | } 54 | } 55 | 56 | node = _ht_create_node(key, connection, table->nodes[index]); 57 | table->nodes[index] = node; 58 | 59 | ++table->size; 60 | 61 | return HT_INSERTED; 62 | } 63 | 64 | bool ht_contains(HashTable* table, int key) { 65 | Node* node; 66 | size_t index; 67 | 68 | if (table->nodes != HT_UNINITIALIZED) { 69 | index = _ht_hash(table, key); 70 | for (node = table->nodes[index]; node; node = node->next) { 71 | if (node->key == key) return HT_FOUND; 72 | } 73 | } 74 | 75 | return HT_NOT_FOUND; 76 | } 77 | 78 | Connection* ht_get(HashTable* table, int key) { 79 | Node* node; 80 | size_t index; 81 | 82 | if (table->nodes == HT_UNINITIALIZED) { 83 | return NULL; 84 | } 85 | 86 | index = _ht_hash(table, key); 87 | node = table->nodes[index]; 88 | 89 | for (; node; node = node->next) { 90 | if (node->key == key) { 91 | return &node->connection; 92 | } 93 | } 94 | 95 | return NULL; 96 | } 97 | 98 | bool ht_remove(HashTable* table, int key) { 99 | Node* node; 100 | Node* previous; 101 | size_t index; 102 | 103 | if (table->nodes == HT_UNINITIALIZED) { 104 | return HT_NOT_FOUND; 105 | } 106 | 107 | index = _ht_hash(table, key); 108 | node = table->nodes[index]; 109 | 110 | for (previous = NULL; node; previous = node, node = node->next) { 111 | if (node->key == key) { 112 | if (previous) { 113 | previous->next = node->next; 114 | } else { 115 | table->nodes[index] = node->next; 116 | } 117 | 118 | free(node); 119 | 120 | if (--table->size == table->threshold / 4) { 121 | _ht_resize(table); 122 | } 123 | 124 | return HT_OK; 125 | } 126 | } 127 | 128 | return HT_NOT_FOUND; 129 | } 130 | 131 | void ht_clear(HashTable* table) { 132 | if (table->nodes == HT_UNINITIALIZED) return; 133 | ht_destroy(table); 134 | _ht_allocate(table, HT_MINIMUM_CAPACITY); 135 | table->size = 0; 136 | } 137 | 138 | bool ht_is_empty(HashTable* table) { 139 | return table->size == 0; 140 | } 141 | 142 | /***** PRIVATE *****/ 143 | 144 | void _ht_create_if_necessary(HashTable* table) { 145 | assert(table != NULL); 146 | if (table->nodes == HT_UNINITIALIZED) { 147 | ht_setup(table, 0); 148 | } 149 | } 150 | 151 | void _ht_allocate(HashTable* table, size_t capacity) { 152 | size_t bytes; 153 | 154 | bytes = sizeof(Node*) * capacity; 155 | table->nodes = (Node**)malloc(bytes); 156 | memset(table->nodes, 0, bytes); 157 | 158 | table->capacity = capacity; 159 | table->threshold = capacity * HT_LOAD_FACTOR; 160 | } 161 | 162 | Node* _ht_create_node(int key, Connection* connection, Node* next) { 163 | Node* node = (Node*)malloc(sizeof(Node)); 164 | 165 | node->key = key; 166 | node->connection = *connection; 167 | node->next = next; 168 | 169 | return node; 170 | } 171 | 172 | size_t _ht_hash(HashTable* table, int key) { 173 | const int a = 69; 174 | const int b = 99; 175 | const int p = 123; 176 | 177 | return (((a * key) + b) % p) % table->capacity; 178 | } 179 | 180 | void _ht_resize(HashTable* table) { 181 | size_t old_capacity = table->capacity; 182 | size_t new_capacity = table->size * 2; 183 | 184 | if (new_capacity < HT_MINIMUM_CAPACITY) return; 185 | 186 | Node** old = table->nodes; 187 | 188 | _ht_allocate(table, new_capacity); 189 | _ht_rehash(table, old, old_capacity); 190 | 191 | free(old); 192 | } 193 | 194 | void _ht_rehash(HashTable* table, Node** old, size_t old_capacity) { 195 | Node* node; 196 | Node* next; 197 | size_t new_index; 198 | 199 | for (int i = 0; i < old_capacity; ++i) { 200 | for (node = old[i]; node;) { 201 | next = node->next; 202 | 203 | new_index = _ht_hash(table, node->key); 204 | node->next = table->nodes[new_index]; 205 | table->nodes[new_index] = node; 206 | 207 | node = next; 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /source/tssx/poll-overrides.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "tssx/bridge.h" 11 | #include "tssx/connection.h" 12 | #include "tssx/poll-overrides.h" 13 | #include "tssx/session.h" 14 | #include "tssx/vector.h" 15 | #include "utility/common.h" 16 | 17 | /******************** REAL FUNCTIONS ********************/ 18 | 19 | int real_poll(struct pollfd fds[], nfds_t nfds, int timeout) { 20 | return ((real_poll_t)dlsym(RTLD_NEXT, "poll"))(fds, nfds, timeout); 21 | } 22 | 23 | /******************** OVERRIDES ********************/ 24 | 25 | int poll(struct pollfd fds[], nfds_t nfds, int timeout) { 26 | Vector tssx_fds, normal_fds; 27 | int event_count; 28 | 29 | if (nfds == 0) return 0; 30 | 31 | if (_partition(&tssx_fds, &normal_fds, fds, nfds) == ERROR) { 32 | return ERROR; 33 | } 34 | 35 | if (tssx_fds.size == 0) { 36 | // We are only dealing with normal (non-tssx) fds 37 | event_count = real_poll(fds, nfds, timeout); 38 | } else if (normal_fds.size == 0) { 39 | // We are only dealing with tssx connections 40 | event_count = _simple_tssx_poll(&tssx_fds, timeout); 41 | } else { 42 | event_count = _concurrent_poll(&tssx_fds, &normal_fds, timeout); 43 | _join_poll_partition(fds, nfds, &normal_fds); 44 | } 45 | 46 | _cleanup(&tssx_fds, &normal_fds); 47 | 48 | return event_count; 49 | } 50 | 51 | /******************** PRIVATE DEFINITIONS ********************/ 52 | 53 | const short _operation_map[2] = {POLLIN, POLLOUT}; 54 | 55 | pthread_cond_t _poll_condition = PTHREAD_COND_INITIALIZER; 56 | pthread_mutex_t _poll_lock = PTHREAD_MUTEX_INITIALIZER; 57 | 58 | bool _normal_thread_ready = false; 59 | bool _poll_is_initialized = false; 60 | 61 | /******************** HELPERS ********************/ 62 | 63 | int _partition(Vector* tssx_fds, 64 | Vector* normal_fds, 65 | struct pollfd fds[], 66 | nfds_t number_of_fds) { 67 | // Minimum capacity of 16 each 68 | if (vector_setup(tssx_fds, 16, sizeof(PollEntry)) == VECTOR_ERROR) { 69 | return ERROR; 70 | } 71 | if (vector_setup(normal_fds, 16, sizeof(struct pollfd)) == VECTOR_ERROR) { 72 | return ERROR; 73 | } 74 | 75 | for (nfds_t index = 0; index < number_of_fds; ++index) { 76 | assert(fds[index].fd > 0); 77 | 78 | // This is necessary for repeated calls with the same poll structures 79 | // (the kernel probably does this internally first too) 80 | fds[index].revents = 0; 81 | 82 | Session* session = bridge_lookup(&bridge, fds[index].fd); 83 | if (session_has_connection(session)) { 84 | PollEntry entry; 85 | entry.connection = session->connection; 86 | entry.poll_pointer = &fds[index]; 87 | vector_push_back(tssx_fds, &entry); 88 | } else { 89 | vector_push_back(normal_fds, &fds[index]); 90 | } 91 | } 92 | 93 | return SUCCESS; 94 | } 95 | 96 | void _join_poll_partition(struct pollfd fds[], 97 | nfds_t number_of_fds, 98 | Vector* normal_fds) { 99 | size_t normal_index = 0; 100 | 101 | for (nfds_t index = 0; index < number_of_fds; ++index) { 102 | Session* session; 103 | struct pollfd* entry; 104 | 105 | session = bridge_lookup(&bridge, fds[index].fd); 106 | if (session_has_connection(session)) continue; 107 | 108 | entry = vector_get(normal_fds, normal_index); 109 | assert(entry->fd == fds[index].fd); 110 | fds[index].revents = entry->revents; 111 | ++normal_index; 112 | } 113 | } 114 | 115 | int _concurrent_poll(Vector* tssx_fds, Vector* normal_fds, int timeout) { 116 | event_count_t event_count = ATOMIC_VAR_INIT(0); 117 | struct sigaction old_action; 118 | pthread_t normal_thread; 119 | PollTask normal_task = {normal_fds, timeout, &event_count}; 120 | PollTask tssx_task = {tssx_fds, timeout, &event_count}; 121 | 122 | if ((_install_poll_signal_handler(&old_action)) == ERROR) return ERROR; 123 | 124 | _normal_thread_ready = false; 125 | 126 | if (_start_normal_poll_thread(&normal_thread, &normal_task) == ERROR) { 127 | return ERROR; 128 | } 129 | 130 | // We need synchronization because it may otherwise occur that the 131 | // main thread (for TSSX) finds an event before the other thread even started 132 | // (or at least started polling). It would then send the kill signal before 133 | // the other thread can catch it. If the other thread has some fds that 134 | // happen to not signal, and has an indefinite timeout, then joining below 135 | // will block forever. Thus we need to make sure the other thread is ready to 136 | // be signalled. 137 | if (_wait_for_normal_thread() == ERROR) return ERROR; 138 | 139 | // Note: Will run in this thread, but deals with concurrent polling 140 | _concurrent_tssx_poll(&tssx_task, normal_thread); 141 | // Theoretically not necessary because we synchronize either through 142 | // the timeout, or via a change on the ready count (quasi condition variable) 143 | // Although, note that POSIX requires a join to reclaim resources, 144 | // unless we detach the thread with pthread_detach to make it a daemon 145 | if (pthread_join(normal_thread, NULL) != SUCCESS) return ERROR; 146 | _restore_old_signal_action(&old_action); 147 | 148 | // Three cases for the ready count 149 | // An error occurred in either polling, then it is -1. 150 | // The timeout expired, then it is 0. 151 | // Either poll found a change, then it is positive. 152 | 153 | return atomic_load(&event_count); 154 | } 155 | 156 | int _start_normal_poll_thread(pthread_t* poll_thread, PollTask* task) { 157 | thread_function_t function = (thread_function_t)_normal_poll; 158 | 159 | if (pthread_create(poll_thread, NULL, function, task) != SUCCESS) { 160 | print_error("Error creating polling thread"); 161 | return ERROR; 162 | } 163 | 164 | return SUCCESS; 165 | } 166 | 167 | void _normal_poll(PollTask* task) { 168 | int local_event_count = 0; 169 | struct pollfd* raw = task->fds->data; 170 | size_t size = task->fds->size; 171 | 172 | assert(task != NULL); 173 | assert(raw != NULL); 174 | 175 | // Tell the main thread that it can start polling now 176 | if (_signal_tssx_thread() == ERROR) { 177 | atomic_store(task->event_count, ERROR); 178 | return; 179 | } 180 | 181 | // Only start polling if in the mean time the other thread 182 | // didn't already find events (and we missed the signal) or 183 | // had an error occur, in which case we also don't want to poll 184 | if (atomic_load(task->event_count) == 0) { 185 | local_event_count = real_poll(raw, size, task->timeout); 186 | } 187 | 188 | // Don't touch anything else if there was an error in the main thread 189 | if (_there_was_an_error(task->event_count)) return; 190 | 191 | // Check if there was an error in real_poll, but not EINTR, which 192 | // would be the signal received when one or more TSSX fd was ready 193 | if (local_event_count == ERROR) { 194 | if (errno != EINTR) { 195 | atomic_store(task->event_count, ERROR); 196 | } 197 | return; 198 | } 199 | 200 | // Either there was a timeout (+= 0), or some FDs are ready 201 | atomic_fetch_add(task->event_count, local_event_count); 202 | } 203 | 204 | int _simple_tssx_poll(Vector* tssx_fds, int timeout) { 205 | size_t start = current_milliseconds(); 206 | int event_count = 0; 207 | 208 | // Do-while for the case of non-blocking (timeout == -1) 209 | // so that we do at least one iteration 210 | 211 | do { 212 | // Do a full loop over all FDs 213 | VECTOR_FOR_EACH(tssx_fds, iterator) { 214 | PollEntry* entry = iterator_get(&iterator); 215 | if (_check_poll_events(entry)) ++event_count; 216 | } 217 | if (event_count > 0) break; 218 | } while (!_poll_timeout_elapsed(start, timeout)); 219 | 220 | return event_count; 221 | } 222 | 223 | void _concurrent_tssx_poll(PollTask* task, pthread_t normal_thread) { 224 | size_t shared_event_count; 225 | size_t start = current_milliseconds(); 226 | size_t local_event_count = 0; 227 | 228 | assert(task != NULL); 229 | assert(task->fds != NULL); 230 | 231 | // Do-while for the case of non-blocking (timeout == -1) 232 | // so that we do at least one iteration 233 | 234 | do { 235 | // Do a full loop over all FDs 236 | VECTOR_FOR_EACH(task->fds, iterator) { 237 | PollEntry* entry = iterator_get(&iterator); 238 | if (_check_poll_events(entry)) ++local_event_count; 239 | } 240 | 241 | shared_event_count = atomic_load(task->event_count); 242 | 243 | // Don't touch if there was an error in the normal thread 244 | if (shared_event_count == ERROR) return; 245 | if (shared_event_count > 0) break; 246 | if (local_event_count > 0) { 247 | _kill_normal_thread(normal_thread); 248 | break; 249 | } 250 | } while (!_poll_timeout_elapsed(start, task->timeout)); 251 | 252 | // Add whatever we have (zero if the timeout elapsed) 253 | atomic_fetch_add(task->event_count, local_event_count); 254 | } 255 | 256 | bool _check_poll_events(PollEntry* entry) { 257 | bool event_occurred = false; 258 | 259 | if (_entry_peer_died(entry)) { 260 | // Note that POLLHUP is output only and ignored in events, 261 | // meaning we must always set it, irrespective of it being 262 | // expected by the user in pollfd.events 263 | entry->poll_pointer->revents |= POLLHUP; 264 | 265 | // If the connection peer died, a call to read() will return 266 | // zero, therefore a hangup event is also a read event 267 | _tell_that_ready_for(entry, READ); 268 | 269 | return true; 270 | } 271 | 272 | if (_check_ready(entry, READ)) event_occurred = true; 273 | if (_check_ready(entry, WRITE)) event_occurred = true; 274 | 275 | return event_occurred; 276 | } 277 | 278 | 279 | bool _check_ready(PollEntry* entry, Operation operation) { 280 | assert(entry != NULL); 281 | if (_waiting_for(entry, operation)) { 282 | // _ready_for here is the polymorphic call (see client/server-overrides) 283 | if (_ready_for(entry->connection, operation)) { 284 | _tell_that_ready_for(entry, operation); 285 | return true; 286 | } 287 | } 288 | 289 | return false; 290 | } 291 | 292 | bool _waiting_for(PollEntry* entry, Operation operation) { 293 | assert(entry != NULL); 294 | assert(entry->poll_pointer != NULL); 295 | return entry->poll_pointer->events & _operation_map[operation]; 296 | } 297 | 298 | bool _tell_that_ready_for(PollEntry* entry, Operation operation) { 299 | return entry->poll_pointer->revents |= _operation_map[operation]; 300 | } 301 | 302 | bool _entry_peer_died(PollEntry* entry) { 303 | assert(entry != NULL); 304 | return connection_peer_died(entry->connection); 305 | } 306 | 307 | void _cleanup(Vector* tssx_fds, Vector* normal_fds) { 308 | vector_destroy(tssx_fds); 309 | vector_destroy(normal_fds); 310 | } 311 | 312 | int _lazy_poll_setup() { 313 | if (!_poll_is_initialized) { 314 | return _setup_poll(); 315 | } 316 | return SUCCESS; 317 | } 318 | 319 | int _setup_poll() { 320 | assert(!_poll_is_initialized); 321 | 322 | if (atexit(_destroy_poll_lock_and_condvar) == ERROR) { 323 | print_error("Error registering destructor with atexit() in poll\n"); 324 | return ERROR; 325 | } 326 | 327 | _poll_is_initialized = true; 328 | 329 | return SUCCESS; 330 | } 331 | 332 | void _destroy_poll_lock_and_condvar() { 333 | pthread_mutex_unlock(&_poll_lock); 334 | pthread_mutex_destroy(&_poll_lock); 335 | pthread_cond_destroy(&_poll_condition); 336 | } 337 | 338 | int _signal_tssx_thread() { 339 | if (pthread_mutex_lock(&_poll_lock) != SUCCESS) { 340 | print_error("Error locking mutex\n"); 341 | return ERROR; 342 | } 343 | 344 | _normal_thread_ready = true; 345 | 346 | if (pthread_cond_signal(&_poll_condition) != SUCCESS) { 347 | print_error("Error signalling main (tssx) thread\n"); 348 | return ERROR; 349 | } 350 | 351 | if (pthread_mutex_unlock(&_poll_lock) != SUCCESS) { 352 | print_error("Error unlocking mutex\n"); 353 | return ERROR; 354 | } 355 | 356 | return SUCCESS; 357 | } 358 | 359 | int _wait_for_normal_thread() { 360 | if (pthread_mutex_lock(&_poll_lock) != SUCCESS) { 361 | print_error("Error locking mutex\n"); 362 | return ERROR; 363 | } 364 | 365 | while (!_normal_thread_ready) { 366 | if (pthread_cond_wait(&_poll_condition, &_poll_lock) != SUCCESS) { 367 | print_error("Error waiting for condition in poll\n"); 368 | return ERROR; 369 | } 370 | } 371 | 372 | if (pthread_mutex_unlock(&_poll_lock) != SUCCESS) { 373 | print_error("Error unlocking mutex\n"); 374 | return ERROR; 375 | } 376 | 377 | return SUCCESS; 378 | } 379 | -------------------------------------------------------------------------------- /source/tssx/reverse-map.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "tssx/reverse-map.h" 6 | 7 | int reverse_map_setup(ReverseMap* reverse) { 8 | assert(reverse != NULL); 9 | memset(reverse, INVALID_KEY, REVERSE_MAP_SIZE); 10 | 11 | return REVERSE_MAP_SUCCESS; 12 | } 13 | 14 | int reverse_map_destroy(ReverseMap* reverse) { 15 | assert(reverse != NULL); 16 | memset(reverse, INVALID_KEY, REVERSE_MAP_SIZE); 17 | 18 | return REVERSE_MAP_SUCCESS; 19 | } 20 | 21 | key_t reverse_map_lookup(ReverseMap* reverse, int socket_fd) { 22 | assert(reverse != NULL); 23 | return (*reverse)[socket_fd]; 24 | } 25 | 26 | int reverse_map_has_entry_for(ReverseMap* reverse, int socket_fd) { 27 | assert(reverse != NULL); 28 | return (*reverse)[socket_fd] != INVALID_KEY; 29 | } 30 | 31 | int reverse_map_erase(ReverseMap* reverse, int socket_fd) { 32 | assert(reverse != NULL); 33 | assert(reverse_map_has_entry_for(reverse, socket_fd)); 34 | (*reverse)[socket_fd] = INVALID_KEY; 35 | 36 | return REVERSE_MAP_SUCCESS; 37 | } 38 | 39 | int reverse_map_insert(ReverseMap* reverse, int socket_fd, key_t key) { 40 | assert(reverse != NULL); 41 | assert(!reverse_map_has_entry_for(reverse, socket_fd)); 42 | (*reverse)[socket_fd] = key; 43 | 44 | return REVERSE_MAP_SUCCESS; 45 | } 46 | 47 | ssize_t reverse_map_size(const ReverseMap* map) { 48 | return REVERSE_MAP_SIZE; 49 | } 50 | 51 | int reverse_map_is_empty(const ReverseMap* map) { 52 | return reverse_map_size(map) == 0; 53 | } 54 | -------------------------------------------------------------------------------- /source/tssx/select-overrides.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "tssx/bridge.h" 12 | #include "tssx/connection.h" 13 | #include "tssx/common-poll-overrides.h" 14 | #include "tssx/poll-overrides.h" 15 | #include "tssx/select-overrides.h" 16 | #include "utility/utility.h" 17 | 18 | /******************** REAL FUNCTION ********************/ 19 | 20 | int real_select(int nfds, 21 | fd_set* readfds, 22 | fd_set* writefds, 23 | fd_set* errorfds, 24 | struct timeval* timeout) { 25 | // clang-format off 26 | return ((real_select_t)dlsym(RTLD_NEXT, "select")) 27 | (nfds, readfds, writefds, errorfds, timeout); 28 | // clang-format on 29 | } 30 | 31 | int real_pselect(int nfds, 32 | fd_set* readfds, 33 | fd_set* writefds, 34 | fd_set* errorfds, 35 | const struct timespec* timeout, 36 | const sigset_t* sigmask) { 37 | // clang-format off 38 | return ((real_pselect_t)dlsym(RTLD_NEXT, "pselect")) 39 | (nfds, readfds, writefds, errorfds, timeout, sigmask); 40 | // clang-format on 41 | } 42 | 43 | /******************** OVERRIDES ********************/ 44 | 45 | int select(int nfds, 46 | fd_set* readfds, 47 | fd_set* writefds, 48 | fd_set* errorfds, 49 | struct timeval* timeout) { 50 | DescriptorSets sets = {readfds, writefds, errorfds}; 51 | size_t tssx_count, normal_count, lowest_fd; 52 | _count_tssx_sockets(nfds, &sets, &lowest_fd, &normal_count, &tssx_count); 53 | 54 | if (normal_count == 0) { 55 | return _select_on_tssx_only(&sets, tssx_count, lowest_fd, nfds, timeout); 56 | } else if (tssx_count == 0) { 57 | return real_select(nfds, readfds, writefds, errorfds, timeout); 58 | } else { 59 | return _forward_to_poll(nfds, &sets, tssx_count + normal_count, timeout); 60 | } 61 | } 62 | 63 | int pselect(int nfds, 64 | fd_set* readfds, 65 | fd_set* writefds, 66 | fd_set* errorfds, 67 | const struct timespec* timeout, 68 | const sigset_t* sigmask) { 69 | struct timeval timeval_timeout; 70 | struct timeval* timeval_pointer; 71 | sigset_t original_mask; 72 | int event_count; 73 | 74 | if (_lazy_select_setup() == ERROR) return ERROR; 75 | 76 | if (timeout == NULL) { 77 | // Block indefinitely 78 | timeval_pointer = NULL; 79 | } else { 80 | timespec_to_timeval(timeout, &timeval_timeout); 81 | timeval_pointer = &timeval_timeout; 82 | } 83 | 84 | if (sigmask == NULL) { 85 | return select(nfds, readfds, writefds, errorfds, timeval_pointer); 86 | } 87 | 88 | if (_set_poll_mask(&_select_lock, sigmask, &original_mask) == ERROR) return ERROR; 89 | 90 | event_count = select(nfds, readfds, writefds, errorfds, timeval_pointer); 91 | 92 | if (_restore_poll_mask(&_select_lock, &original_mask) == ERROR) return ERROR; 93 | 94 | return event_count; 95 | } 96 | 97 | /******************** PRIVATE DEFINITIONS ********************/ 98 | 99 | pthread_mutex_t _select_lock = PTHREAD_MUTEX_INITIALIZER; 100 | bool _select_is_initialized = false; 101 | 102 | /******************** HELPERS ********************/ 103 | 104 | int _forward_to_poll(size_t highest_fd, 105 | DescriptorSets* sets, 106 | size_t population_count, 107 | struct timeval* timeout) { 108 | struct pollfd* poll_entries; 109 | size_t number_of_events; 110 | int milliseconds; 111 | 112 | poll_entries = _setup_poll_entries(population_count, sets, highest_fd); 113 | if (poll_entries == NULL) { 114 | return ERROR; 115 | } 116 | 117 | milliseconds = timeout ? timeval_to_milliseconds(timeout) : BLOCK_FOREVER; 118 | 119 | // The actual forwarding call 120 | number_of_events = poll(poll_entries, population_count, milliseconds); 121 | 122 | if (number_of_events == ERROR) { 123 | free(poll_entries); 124 | return ERROR; 125 | } 126 | 127 | if (_read_poll_entries(sets, poll_entries, population_count) == ERROR) { 128 | return ERROR; 129 | } 130 | 131 | return number_of_events; 132 | } 133 | 134 | struct pollfd* _setup_poll_entries(size_t population_count, 135 | const DescriptorSets* sets, 136 | size_t highest_fd) { 137 | struct pollfd* poll_entries; 138 | 139 | poll_entries = calloc(population_count, sizeof *poll_entries); 140 | if (poll_entries == NULL) { 141 | perror("Error allocating memory for poll entries"); 142 | return NULL; 143 | } 144 | 145 | _fill_poll_entries(poll_entries, sets, highest_fd); 146 | 147 | return poll_entries; 148 | } 149 | 150 | int _read_poll_entries(DescriptorSets* sets, 151 | struct pollfd* poll_entries, 152 | size_t population_count) { 153 | // First unset all, then just repopulate 154 | _clear_all_sets(sets); 155 | 156 | for (size_t index = 0; index < population_count; ++index) { 157 | struct pollfd* entry = &(poll_entries[index]); 158 | 159 | if (_check_poll_event_occurred(entry, sets, POLLNVAL)) continue; 160 | _check_poll_event_occurred(entry, sets, POLLIN); 161 | _check_poll_event_occurred(entry, sets, POLLOUT); 162 | _check_poll_event_occurred(entry, sets, POLLERR); 163 | } 164 | 165 | free(poll_entries); 166 | 167 | return SUCCESS; 168 | } 169 | 170 | void _fill_poll_entries(struct pollfd* poll_entries, 171 | const DescriptorSets* sets, 172 | size_t highest_fd) { 173 | size_t poll_index = 0; 174 | 175 | for (size_t fd = 0; fd < highest_fd; ++fd) { 176 | if (!_is_in_any_set(fd, sets)) continue; 177 | 178 | poll_entries[poll_index].fd = fd; 179 | 180 | if (_fd_is_set(fd, sets->readfds)) { 181 | poll_entries[poll_index].events |= POLLIN; 182 | } 183 | if (_fd_is_set(fd, sets->writefds)) { 184 | poll_entries[poll_index].events |= POLLOUT; 185 | } 186 | if (_fd_is_set(fd, sets->errorfds)) { 187 | poll_entries[poll_index].events |= POLLERR; 188 | } 189 | 190 | ++poll_index; 191 | } 192 | } 193 | 194 | int _select_on_tssx_only(DescriptorSets* sets, 195 | size_t tssx_count, 196 | size_t lowest_fd, 197 | size_t highest_fd, 198 | struct timeval* timeout) { 199 | if (tssx_count == 1) { 200 | assert(lowest_fd + 1 == highest_fd); 201 | return _select_on_tssx_only_fast_path(sets, lowest_fd, timeout); 202 | } 203 | 204 | size_t start = current_milliseconds(); 205 | int ready_count = 0; 206 | int milliseconds = timeout ? timeval_to_milliseconds(timeout) : BLOCK_FOREVER; 207 | 208 | fd_set readfds, writefds, errorfds; 209 | DescriptorSets original = {&readfds, &writefds, &errorfds}; 210 | _copy_all_sets(&original, sets); 211 | _clear_all_sets(sets); 212 | 213 | // Do-while for the case of non-blocking 214 | // so that we do at least one iteration 215 | do { 216 | for (size_t fd = lowest_fd; fd < highest_fd; ++fd) { 217 | Session* session = bridge_lookup(&bridge, fd); 218 | if (_check_select_events(fd, session, &original)) ++ready_count; 219 | } 220 | if (ready_count > 0) break; 221 | } while (!_select_timeout_elapsed(start, milliseconds)); 222 | 223 | return ready_count; 224 | } 225 | 226 | int _select_on_tssx_only_fast_path(DescriptorSets* sets, 227 | size_t fd, 228 | struct timeval* timeout) { 229 | bool ready; 230 | bool select_read = _fd_is_set(fd, sets->readfds); 231 | bool select_write = _fd_is_set(fd, sets->writefds); 232 | assert(select_read || select_write); 233 | 234 | _clear_all_sets(sets); 235 | 236 | size_t start = current_milliseconds(); 237 | int milliseconds = timeout ? timeval_to_milliseconds(timeout) : BLOCK_FOREVER; 238 | 239 | Session* session = bridge_lookup(&bridge, fd); 240 | 241 | ready = false; 242 | if (select_read && select_write) { 243 | do { 244 | if (_select_peer_died(fd, session, sets)) return true; 245 | if (_ready_for_select(fd, session, sets->writefds, WRITE)) ready = true; 246 | if (_ready_for_select(fd, session, sets->readfds, READ)) ready = true; 247 | } while (!ready && !_select_timeout_elapsed(start, milliseconds)); 248 | } else if (select_read) { 249 | do { 250 | if (_select_peer_died(fd, session, sets)) return true; 251 | if (_ready_for_select(fd, session, sets->readfds, READ)) return true; 252 | } while (!_select_timeout_elapsed(start, milliseconds)); 253 | } else { 254 | do { 255 | if (_select_peer_died(fd, session, sets)) return true; 256 | if (_ready_for_select(fd, session, sets->writefds, WRITE)) return true; 257 | } while (!_select_timeout_elapsed(start, milliseconds)); 258 | } 259 | 260 | return ready; 261 | } 262 | 263 | void _count_tssx_sockets(size_t highest_fd, 264 | const DescriptorSets* sets, 265 | size_t* lowest_fd, 266 | size_t* normal_count, 267 | size_t* tssx_count) { 268 | *normal_count = 0; 269 | *tssx_count = 0; 270 | *lowest_fd = highest_fd; 271 | 272 | for (size_t fd = 0; fd < highest_fd; ++fd) { 273 | if (_is_in_any_set(fd, sets)) { 274 | if (fd < *lowest_fd) { 275 | *lowest_fd = fd; 276 | } 277 | if (bridge_has_connection(&bridge, fd)) { 278 | ++(*tssx_count); 279 | } else { 280 | ++(*normal_count); 281 | } 282 | } 283 | } 284 | } 285 | 286 | void _copy_all_sets(DescriptorSets* destination, const DescriptorSets* source) { 287 | _copy_set(destination->readfds, source->readfds); 288 | _copy_set(destination->writefds, source->writefds); 289 | _copy_set(destination->errorfds, source->errorfds); 290 | } 291 | 292 | void _copy_set(fd_set* destination, const fd_set* source) { 293 | if (source == NULL) { 294 | FD_ZERO(destination); 295 | } else { 296 | *destination = *source; 297 | } 298 | } 299 | 300 | void _clear_all_sets(DescriptorSets* sets) { 301 | _clear_set(sets->readfds); 302 | _clear_set(sets->writefds); 303 | _clear_set(sets->errorfds); 304 | } 305 | 306 | bool _is_in_any_set(int fd, const DescriptorSets* sets) { 307 | if (_fd_is_set(fd, sets->readfds)) return true; 308 | if (_fd_is_set(fd, sets->writefds)) return true; 309 | if (_fd_is_set(fd, sets->errorfds)) return true; 310 | 311 | return false; 312 | } 313 | 314 | bool _fd_is_set(int fd, const fd_set* set) { 315 | return set != NULL && FD_ISSET(fd, set); 316 | } 317 | 318 | bool _select_timeout_elapsed(size_t start, int timeout) { 319 | if (timeout == BLOCK_FOREVER) return false; 320 | if (timeout == DONT_BLOCK) return true; 321 | 322 | return (current_milliseconds() - start) > timeout; 323 | } 324 | 325 | void _clear_set(fd_set* set) { 326 | if (set != NULL) FD_ZERO(set); 327 | } 328 | 329 | bool _check_select_events(int fd, Session* session, DescriptorSets* sets) { 330 | bool activity = false; 331 | 332 | assert(session != NULL); 333 | assert(sets != NULL); 334 | 335 | if (_select_peer_died(fd, session, sets)) return true; 336 | 337 | if (_waiting_and_ready_for_select(fd, session, sets, READ)) { 338 | activity = true; 339 | } 340 | 341 | if (_waiting_and_ready_for_select(fd, session, sets, WRITE)) { 342 | activity = true; 343 | } 344 | 345 | return activity; 346 | } 347 | 348 | bool _select_peer_died(int fd, Session* session, DescriptorSets* sets) { 349 | if (connection_peer_died(session->connection)) { 350 | // When the connection hangs up (open-count drops to one) 351 | // a read event should be triggered, as read() will return zero 352 | FD_SET(fd, sets->readfds); 353 | return true; 354 | } 355 | 356 | return false; 357 | } 358 | 359 | bool _waiting_and_ready_for_select(int fd, 360 | Session* session, 361 | const DescriptorSets* sets, 362 | Operation operation) { 363 | fd_set* set; 364 | 365 | set = _fd_set_for_operation(sets, operation); 366 | if (!_fd_is_set(fd, set)) return false; 367 | 368 | return _ready_for_select(fd, session, set, operation); 369 | } 370 | 371 | bool _ready_for_select(int fd, 372 | Session* session, 373 | fd_set* set, 374 | Operation operation) { 375 | if (_ready_for(session->connection, operation)) { 376 | FD_SET(fd, set); 377 | return true; 378 | } 379 | 380 | return false; 381 | } 382 | 383 | bool _check_poll_event_occurred(const struct pollfd* entry, 384 | DescriptorSets* sets, 385 | int event) { 386 | fd_set* set; 387 | 388 | assert(entry != NULL); 389 | assert(sets != NULL); 390 | assert(event == POLLIN || event == POLLOUT || event == POLLERR || 391 | event == POLLNVAL); 392 | 393 | if (entry->revents & event) { 394 | set = _fd_set_for_poll_event(sets, event); 395 | FD_SET(entry->fd, set); 396 | return true; 397 | } 398 | 399 | return false; 400 | } 401 | 402 | fd_set* _fd_set_for_operation(const DescriptorSets* sets, Operation operation) { 403 | return (operation == READ) ? sets->readfds : sets->writefds; 404 | } 405 | 406 | fd_set* _fd_set_for_poll_event(const DescriptorSets* sets, int poll_event) { 407 | switch (poll_event) { 408 | case POLLNVAL: return sets->errorfds; 409 | case POLLIN: return sets->readfds; 410 | case POLLOUT: return sets->writefds; 411 | case POLLERR: return sets->errorfds; 412 | default: assert(false); return NULL; 413 | } 414 | } 415 | 416 | int _lazy_select_setup() { 417 | if (!_select_is_initialized) { 418 | return _setup_select(); 419 | } 420 | 421 | return SUCCESS; 422 | } 423 | 424 | int _setup_select() { 425 | assert(!_select_is_initialized); 426 | 427 | if (atexit(_destroy_select_lock) == ERROR) { 428 | print_error("Error registering destructor with atexit() in select\n"); 429 | return ERROR; 430 | } 431 | 432 | _select_is_initialized = true; 433 | 434 | return SUCCESS; 435 | } 436 | 437 | void _destroy_select_lock() { 438 | pthread_mutex_unlock(&_select_lock); 439 | pthread_mutex_destroy(&_select_lock); 440 | } 441 | -------------------------------------------------------------------------------- /source/tssx/selective.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "tssx/selective.h" 10 | #include "tssx/string-set.h" 11 | #include "utility/utility.h" 12 | 13 | StringSet selective_set = SS_INITIALIZER; 14 | 15 | int check_tssx_usage(int fd, Side side) { 16 | struct sockaddr_storage address; 17 | size_t length; 18 | int return_code; 19 | 20 | length = _get_socket_address(fd, &address, side); 21 | 22 | if ((return_code = _is_domain_and_stream_socket(fd, &address)) != true) { 23 | // Either error (-1) or false (0) (the socket is not a domain socket) 24 | return return_code; 25 | } 26 | 27 | if (!ss_is_initialized(&selective_set)) { 28 | _initialize_selective_set(); 29 | } 30 | 31 | // Empty means all sockets should use TSSX 32 | return _in_selective_set(&address, length); 33 | } 34 | 35 | void _initialize_selective_set() { 36 | const char* variable; 37 | if ((variable = _fetch_tssx_variable()) != NULL) { 38 | _parse_tssx_variable(variable); 39 | } 40 | 41 | #ifdef DEBUG 42 | if (variable == NULL) { 43 | fprintf(stderr, "'USE_TSSX' not found, disabling TSSX by default ...\n"); 44 | } 45 | #endif 46 | } 47 | 48 | const char* _fetch_tssx_variable() { 49 | return getenv("USE_TSSX"); 50 | } 51 | 52 | void _parse_tssx_variable(const char* variable) { 53 | char* buffer; 54 | 55 | // Not allowed to change the variable buffer 56 | buffer = malloc(strlen(variable)); 57 | strcpy(buffer, variable); 58 | 59 | const char* path; 60 | for (path = strtok(buffer, " "); path; path = strtok(NULL, " ")) { 61 | #ifdef DEBUG 62 | assert(ss_insert(&selective_set, path)); 63 | #else 64 | ss_insert(&selective_set, path); 65 | #endif 66 | } 67 | 68 | free(buffer); 69 | } 70 | 71 | int _in_selective_set(struct sockaddr_storage* address, size_t length) { 72 | struct sockaddr_un* domain_address = (struct sockaddr_un*)address; 73 | 74 | // Seems this really is necessary 75 | _insert_null_terminator(domain_address, length); 76 | 77 | #ifdef DEBUG 78 | if (ss_contains(&selective_set, domain_address->sun_path)) { 79 | fprintf(stderr, "Enabling TSSX for '%s' ...\n", domain_address->sun_path); 80 | } else { 81 | fprintf(stderr, "Disabling TSSX for '%s' ...\n", domain_address->sun_path); 82 | } 83 | #endif 84 | 85 | return ss_contains(&selective_set, domain_address->sun_path); 86 | } 87 | 88 | int _is_domain_and_stream_socket(int fd, struct sockaddr_storage* address) { 89 | return _is_domain_socket(address) && _is_stream_socket(fd); 90 | } 91 | 92 | int _is_domain_socket(struct sockaddr_storage* address) { 93 | // AF_LOCAL is an alias for AF_UNIX (i.e. they have the same value) 94 | return address->ss_family == AF_LOCAL; 95 | } 96 | 97 | int _is_stream_socket(int fd) { 98 | int option = _get_socket_option(fd, SO_TYPE); 99 | return option == ERROR ? ERROR : (option == SOCK_STREAM); 100 | } 101 | 102 | int _get_socket_option(int fd, int option_name) { 103 | int return_code; 104 | socklen_t option; 105 | socklen_t option_length = sizeof option; 106 | 107 | // clang-format off 108 | return_code = getsockopt( 109 | fd, 110 | SOL_SOCKET, 111 | option_name, 112 | &option, 113 | &option_length 114 | ); 115 | // clang-format on 116 | 117 | if (return_code == ERROR) { 118 | print_error("Error getting socket option"); 119 | return ERROR; 120 | } 121 | 122 | return option; 123 | } 124 | 125 | int _get_socket_address(int fd, struct sockaddr_storage* address, Side side) { 126 | socklen_t length = sizeof *address; 127 | int return_code; 128 | 129 | if (side == SERVER) { 130 | return_code = getsockname(fd, (struct sockaddr*)address, &length); 131 | } else { 132 | return_code = getpeername(fd, (struct sockaddr*)address, &length); 133 | } 134 | 135 | if (return_code == ERROR) { 136 | fprintf(stderr, "Error getting socket address"); 137 | return ERROR; 138 | } 139 | 140 | return length; 141 | } 142 | 143 | void _insert_null_terminator(struct sockaddr_un* address, size_t length) { 144 | length -= offsetof(struct sockaddr_un, sun_path); 145 | address->sun_path[length] = '\0'; 146 | } 147 | -------------------------------------------------------------------------------- /source/tssx/server-overrides.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "tssx/common-overrides.h" 5 | #include "tssx/server-overrides.h" 6 | #include "utility/sockets.h" 7 | 8 | 9 | /******************** OVERRIDES ********************/ 10 | 11 | int accept(int server_socket, sockaddr *address, socklen_t *length) { 12 | int client_socket; 13 | int use_tssx; 14 | 15 | if ((client_socket = real_accept(server_socket, address, length)) == ERROR) { 16 | return ERROR; 17 | } 18 | 19 | if ((use_tssx = check_tssx_usage(server_socket, SERVER)) == ERROR) { 20 | print_error("Could not check if server uses TSSX\n"); 21 | return ERROR; 22 | } else if (!use_tssx) { 23 | return client_socket; 24 | } 25 | 26 | if (_setup_tssx(client_socket) == ERROR) { 27 | return ERROR; 28 | } 29 | 30 | return client_socket; 31 | } 32 | 33 | ssize_t read(int key, void *destination, size_t requested_bytes) { 34 | // clang-format off 35 | return connection_read( 36 | key, 37 | destination, 38 | requested_bytes, 39 | CLIENT_BUFFER 40 | ); 41 | // clang-format on 42 | } 43 | 44 | ssize_t write(int key, const void *source, size_t requested_bytes) { 45 | // clang-format off 46 | return connection_write( 47 | key, 48 | source, 49 | requested_bytes, 50 | SERVER_BUFFER 51 | ); 52 | // clang-format on 53 | } 54 | 55 | /******************** HELPERS ********************/ 56 | 57 | int _setup_tssx(int client_socket) { 58 | Session session = SESSION_INITIALIZER; 59 | ConnectionOptions options; 60 | 61 | options = options_from_socket(client_socket, SERVER); 62 | session.connection = create_connection(&options); 63 | 64 | if (session.connection == NULL) { 65 | print_error("Error allocating connection resources"); 66 | return ERROR; 67 | } 68 | 69 | if (_send_segment_id_to_client(client_socket, &session) == ERROR) { 70 | print_error("Error sending segment ID to client"); 71 | return ERROR; 72 | } 73 | 74 | if (_synchronize_with_client(client_socket) == ERROR) { 75 | print_error("Error synchronizing with client during setup\n"); 76 | return ERROR; 77 | } 78 | 79 | return bridge_insert(&bridge, client_socket, &session); 80 | } 81 | 82 | int _send_segment_id_to_client(int client_socket, Session *session) { 83 | int return_code; 84 | 85 | // clang-format off 86 | return_code = real_write( 87 | client_socket, 88 | &session->connection->segment_id, 89 | sizeof session->connection->segment_id 90 | ); 91 | // clang-format on 92 | 93 | if (return_code == ERROR) { 94 | perror("Error sending segment ID to client"); 95 | disconnect(session->connection); 96 | return ERROR; 97 | } 98 | 99 | return return_code; 100 | } 101 | 102 | int _synchronize_with_client(int client_fd) { 103 | char message; 104 | 105 | unset_socket_non_blocking(client_fd); 106 | 107 | // Expecting just a single synchronization byte 108 | if (real_read(client_fd, &message, 1) == ERROR) { 109 | perror("Foo"); 110 | return ERROR; 111 | } 112 | 113 | // Put the old flags back in place 114 | // set_socket_flags(client_socket, flags); 115 | 116 | return SUCCESS; 117 | } 118 | 119 | /******************** "POLYMORPHIC" FUNCTIONS ********************/ 120 | 121 | void set_non_blocking(Connection *connection, bool non_blocking) { 122 | connection->server_buffer->timeouts.non_blocking[WRITE] = non_blocking; 123 | connection->client_buffer->timeouts.non_blocking[READ] = non_blocking; 124 | } 125 | 126 | bool is_non_blocking(Connection *connection) { 127 | assert(connection->server_buffer->timeouts.non_blocking[WRITE] == 128 | connection->client_buffer->timeouts.non_blocking[READ]); 129 | return connection->server_buffer->timeouts.non_blocking[WRITE]; 130 | } 131 | 132 | bool _ready_for(Connection *connection, Operation operation) { 133 | assert(connection != NULL); 134 | if (operation == READ) { 135 | return buffer_ready_for(connection->client_buffer, READ); 136 | } else { 137 | return buffer_ready_for(connection->server_buffer, WRITE); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /source/tssx/session-table.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "tssx/session-table.h" 4 | #include "tssx/session.h" 5 | #include "tssx/vector.h" 6 | #include "utility/utility.h" 7 | 8 | void session_table_setup(SessionTable* table) { 9 | assert(table != NULL); 10 | for (size_t index = 0; index < SESSION_TABLE_SIZE; ++index) { 11 | Session* session = &(*table)[index]; 12 | session_setup(session); 13 | } 14 | } 15 | 16 | void session_table_destroy(SessionTable* table) { 17 | assert(table != NULL); 18 | for (size_t index = 0; index < SESSION_TABLE_SIZE; ++index) { 19 | Session* session = &(*table)[index]; 20 | session_invalidate(session); 21 | } 22 | } 23 | 24 | void session_table_assign(SessionTable* table, size_t index, Session* session) { 25 | assert(table != NULL); 26 | assert(index < SESSION_TABLE_SIZE); 27 | assert((*table)[index].connection == NULL); 28 | (*table)[index] = *session; 29 | } 30 | 31 | Session* session_table_get(SessionTable* table, size_t index) { 32 | assert(table != NULL); 33 | assert(index < SESSION_TABLE_SIZE); 34 | return &((*table)[index]); 35 | } 36 | -------------------------------------------------------------------------------- /source/tssx/session.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "tssx/connection.h" 5 | #include "tssx/session.h" 6 | 7 | void session_setup(Session* session) { 8 | assert(!session_has_connection(session)); 9 | session->connection = NULL; 10 | } 11 | 12 | bool session_has_connection(const Session* session) { 13 | assert(session != NULL); 14 | return session->connection != NULL; 15 | } 16 | 17 | void session_invalidate(Session* session) { 18 | if (!session_has_connection(session)) return; 19 | 20 | disconnect(session->connection); 21 | free(session->connection); 22 | session->connection = NULL; 23 | } 24 | -------------------------------------------------------------------------------- /source/tssx/shared-memory.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 500 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "tssx/buffer.h" 11 | #include "tssx/definitions.h" 12 | #include "tssx/shared-memory.h" 13 | #include "utility/utility.h" 14 | 15 | #define SHM_FLAGS IPC_CREAT | IPC_EXCL | 0666 16 | 17 | #include 18 | 19 | int create_segment(int total_size) { 20 | int id; 21 | 22 | assert(total_size > 0); 23 | 24 | // Generate a random key until it is not taken yet 25 | while ((id = shmget(rand(), total_size, SHM_FLAGS)) == ERROR) { 26 | if (errno != EEXIST && errno != EINVAL && errno != EACCES) { 27 | throw("Error creating segment"); 28 | } 29 | // else just keep generating new keys (this one was taken) 30 | } 31 | 32 | return id; 33 | } 34 | 35 | void* attach_segment(int segment_id) { 36 | void* shared_memory; 37 | 38 | if ((shared_memory = shmat(segment_id, NULL, 0)) == (void*)-1) { 39 | throw("Error attaching shared memory segment to address space"); 40 | } 41 | 42 | return shared_memory; 43 | } 44 | 45 | void detach_segment(void* shared_memory) { 46 | if (shmdt(shared_memory) == ERROR) { 47 | throw("Error detaching shared memory segment"); 48 | } 49 | } 50 | 51 | void destroy_segment(int segment_id) { 52 | if (shmctl(segment_id, IPC_RMID, NULL) == ERROR) { 53 | throw("Error destroying shared memory segment"); 54 | } 55 | } 56 | 57 | int segment_size(Buffer* buffer) { 58 | assert(buffer->capacity > 0); 59 | return sizeof(Buffer) + buffer->capacity; 60 | } 61 | -------------------------------------------------------------------------------- /source/tssx/socket-overrides.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "tssx/bridge.h" 12 | #include "tssx/connection.h" 13 | #include "tssx/session.h" 14 | #include "tssx/socket-overrides.h" 15 | #include "utility/utility.h" 16 | 17 | #ifdef __linux__ 18 | #include "tssx/epoll-overrides.h" 19 | #endif 20 | 21 | /******************** REAL FUNCTIONS ********************/ 22 | 23 | // RTDL_NEXT = look in the symbol table of the *next* object file after this one 24 | ssize_t real_write(int fd, const void* data, size_t size) { 25 | return ((real_write_t)dlsym(RTLD_NEXT, "write"))(fd, data, size); 26 | } 27 | 28 | ssize_t real_read(int fd, void* data, size_t size) { 29 | return ((real_read_t)dlsym(RTLD_NEXT, "read"))(fd, data, size); 30 | } 31 | 32 | ssize_t real_send(int fd, const void* buffer, size_t length, int flags) { 33 | return ((real_send_t)dlsym(RTLD_NEXT, "send"))(fd, buffer, length, flags); 34 | } 35 | 36 | ssize_t real_recv(int fd, void* buffer, size_t length, int flags) { 37 | return ((real_recv_t)dlsym(RTLD_NEXT, "recv"))(fd, buffer, length, flags); 38 | } 39 | 40 | ssize_t real_sendmsg(int fd, const struct msghdr* message, int flags) { 41 | return ((real_sendmsg_t)dlsym(RTLD_NEXT, "sendmsg"))(fd, message, flags); 42 | } 43 | 44 | ssize_t real_recvmsg(int fd, struct msghdr* message, int flags) { 45 | return ((real_recvmsg_t)dlsym(RTLD_NEXT, "recvmsg"))(fd, message, flags); 46 | } 47 | 48 | ssize_t real_sendto(int fd, 49 | const void* buffer, 50 | size_t length, 51 | int flags, 52 | const struct sockaddr* dest_addr, 53 | socklen_t dest_len) { 54 | // clang-format off 55 | return ((real_sendto_t)dlsym(RTLD_NEXT, "sendto")) 56 | (fd, buffer, length, flags, dest_addr, dest_len); 57 | // clang-format on 58 | } 59 | 60 | ssize_t real_recvfrom(int fd, 61 | void* restrict buffer, 62 | size_t length, 63 | int flags, 64 | struct sockaddr* restrict address, 65 | socklen_t* restrict address_len) { 66 | // clang-format off 67 | return ((real_recvfrom_t)dlsym(RTLD_NEXT, "recvfrom")) 68 | (fd, buffer, length, flags, address, address_len); 69 | // clang-format on 70 | } 71 | 72 | int real_accept(int fd, sockaddr* address, socklen_t* length) { 73 | return ((real_accept_t)dlsym(RTLD_NEXT, "accept"))(fd, address, length); 74 | } 75 | 76 | int real_connect(int fd, const sockaddr* address, socklen_t length) { 77 | return ((real_connect_t)dlsym(RTLD_NEXT, "connect"))(fd, address, length); 78 | } 79 | 80 | int real_close(int fd) { 81 | return ((real_close_t)dlsym(RTLD_NEXT, "close"))(fd); 82 | } 83 | 84 | int real_getsockopt(int fd, 85 | int level, 86 | int option_name, 87 | void* restrict option_value, 88 | socklen_t* restrict option_len) { 89 | // Some nice lisp here 90 | // clang-format off 91 | return ((real_getsockopt_t)dlsym(RTLD_NEXT, "getsockopt")) 92 | (fd, level, option_name, option_value, option_len); 93 | // clang-format on 94 | } 95 | 96 | int real_setsockopt(int fd, 97 | int level, 98 | int option_name, 99 | const void* option_value, 100 | socklen_t option_len) { 101 | // Some nice lisp here 102 | // clang-format off 103 | return ((real_setsockopt_t)dlsym(RTLD_NEXT, "setsockopt")) 104 | (fd, level, option_name, option_value, option_len); 105 | // clang-format on 106 | } 107 | 108 | int real_getsockname(int fd, struct sockaddr* addr, socklen_t* addrlen) { 109 | // Some nice lisp here 110 | // clang-format off 111 | return ((real_getsockname_t)dlsym(RTLD_NEXT, "getsockname")) 112 | (fd, addr, addrlen); 113 | // clang-format on 114 | } 115 | 116 | /******************** COMMON OVERRIDES ********************/ 117 | 118 | int getsockopt(int fd, 119 | int level, 120 | int option_name, 121 | void* restrict option_value, 122 | socklen_t* restrict option_len) { 123 | // clang-format off 124 | return real_getsockopt( 125 | fd, 126 | level, 127 | option_name, 128 | option_value, 129 | option_len 130 | ); 131 | // clang-format on 132 | } 133 | 134 | int getsockname(int fd, struct sockaddr* addr, socklen_t* addrlen) { 135 | return real_getsockname(fd, addr, addrlen); 136 | } 137 | 138 | int setsockopt(int fd, 139 | int level, 140 | int option_name, 141 | const void* option_value, 142 | socklen_t option_len) { 143 | // clang-format off 144 | return real_setsockopt( 145 | fd, 146 | level, 147 | option_name, 148 | option_value, 149 | option_len 150 | ); 151 | // clang-fomat pm 152 | } 153 | 154 | int close(int fd) { 155 | // epoll is linux only 156 | #ifdef __linux__ 157 | // These two are definitely mutually exclusive 158 | if (has_epoll_instance_associated(fd)) { 159 | close_epoll_instance(fd); 160 | } else { 161 | bridge_erase(&bridge, fd); 162 | } 163 | #else 164 | bridge_erase(&bridge, fd); 165 | #endif 166 | return real_close(fd); 167 | } 168 | 169 | ssize_t send(int fd, const void* buffer, size_t length, int flags) { 170 | // For now: We forward the call to write for a certain set of 171 | // flags, which we chose to ignore. By putting them here explicitly, 172 | // we make sure that we only ignore flags, which are not important. 173 | // For production, we might wanna handle these flags 174 | #ifdef __APPLE__ 175 | if (flags == 0) { 176 | #else 177 | if (flags == 0 || flags == MSG_NOSIGNAL) { 178 | #endif 179 | return write(fd, buffer, length); 180 | } else { 181 | warn("Routing send to socket (unsupported flags)"); 182 | return real_send(fd, buffer, length, flags); 183 | } 184 | } 185 | 186 | ssize_t recv(int fd, void *buffer, size_t length, int flags) { 187 | #ifdef __APPLE__ 188 | if (flags == 0) { 189 | #else 190 | if (flags == 0 || flags == MSG_NOSIGNAL) { 191 | #endif 192 | return read(fd, buffer, length); 193 | } else { 194 | warn("Routing recv to socket (unsupported flags)"); 195 | return real_recv(fd, buffer, length, flags); 196 | } 197 | } 198 | 199 | ssize_t sendto(int fd, 200 | const void *buffer, 201 | size_t length, 202 | int flags, 203 | const struct sockaddr *dest_addr, 204 | socklen_t addrlen) { 205 | // When the destination address is null, then this should be a stream socket 206 | if (dest_addr == NULL) { 207 | return send(fd, buffer, length, flags); 208 | } else { 209 | // Connection-less sockets (UDP) sockets never use TSSX anyway 210 | return real_sendto(fd, buffer, length, flags, dest_addr, addrlen); 211 | } 212 | } 213 | 214 | ssize_t recvfrom(int fd, 215 | void *buffer, 216 | size_t length, 217 | int flags, 218 | struct sockaddr *src_addr, 219 | socklen_t *addrlen) { 220 | // When the destination address is null, then this should be a stream socket 221 | if (src_addr == NULL) { 222 | return recv(fd, buffer, length, flags); 223 | } else { 224 | // Connection-Less sockets (UDP) sockets never use TSSX anyway 225 | return real_recvfrom(fd, buffer, length, flags, src_addr, addrlen); 226 | } 227 | } 228 | 229 | ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) { 230 | // This one is hard to implemenet because the `msghdr` struct contains 231 | // an iovec pointer, which points to an array of iovec structs. Each such 232 | // struct is then a vector with a starting address and length. The sendmsg 233 | // call then fills these vectors one by one until the stream is empty or 234 | // all the vectors have been filled. I don't know how many people use this 235 | // function, but right now we just support a single buffer and else route 236 | // the call to the socket itself. 237 | if (msg->msg_iovlen == 1) { 238 | return sendto(fd, msg->msg_iov[0].iov_base, msg->msg_iov[0].iov_len, flags, (struct sockaddr*)msg->msg_name, msg->msg_namelen); 239 | } else { 240 | warn("Routing sendmsg to socket (too many buffers)"); 241 | return real_sendmsg(fd, msg, flags); 242 | } 243 | } 244 | 245 | ssize_t recvmsg(int fd, struct msghdr *msg, int flags) { 246 | if (msg->msg_iovlen == 1) { 247 | return recvfrom(fd, msg->msg_iov[0].iov_base, msg->msg_iov[0].iov_len, flags, (struct sockaddr*)msg->msg_name, &msg->msg_namelen); 248 | } else { 249 | warn("Routing recvmsg to socket (too many buffers)"); 250 | return real_recvmsg(fd, msg, flags); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /source/tssx/string-set.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "tssx/string-set.h" 7 | 8 | void ss_setup(StringSet* set, size_t capacity) { 9 | assert(set != NULL); 10 | 11 | if (capacity < SS_MINIMUM_CAPACITY) { 12 | capacity = SS_MINIMUM_CAPACITY; 13 | } 14 | 15 | _ss_allocate(set, capacity); 16 | set->size = 0; 17 | } 18 | 19 | void ss_destroy(StringSet* set) { 20 | Node* node; 21 | Node* next; 22 | 23 | assert(set != NULL); 24 | 25 | for (size_t i = 0; i < set->capacity; ++i) { 26 | node = set->nodes[i]; 27 | while (node) { 28 | next = node->next; 29 | free(node); 30 | node = next; 31 | } 32 | } 33 | 34 | free(set->nodes); 35 | } 36 | 37 | bool ss_insert(StringSet* set, String key) { 38 | Node* node; 39 | size_t index; 40 | 41 | _ss_create_if_necessary(set); 42 | 43 | if (set->size == set->threshold) { 44 | _ss_resize(set); 45 | } 46 | 47 | index = _ss_hash(set, key); 48 | node = set->nodes[index]; 49 | 50 | for (; node; node = node->next) { 51 | if (_ss_equals(node->key, key)) { 52 | return SS_NOT_INSERTED; 53 | } 54 | } 55 | 56 | node = _ss_create_node(key, set->nodes[index]); 57 | set->nodes[index] = node; 58 | 59 | ++set->size; 60 | 61 | return SS_INSERTED; 62 | } 63 | 64 | bool ss_contains(StringSet* set, String key) { 65 | Node* node; 66 | size_t index; 67 | 68 | if (set->nodes != SS_UNINITIALIZED) { 69 | index = _ss_hash(set, key); 70 | for (node = set->nodes[index]; node; node = node->next) { 71 | if (_ss_equals(node->key, key)) return SS_FOUND; 72 | } 73 | } 74 | 75 | return SS_NOT_FOUND; 76 | } 77 | 78 | bool ss_remove(StringSet* set, String key) { 79 | Node* node; 80 | Node* previous; 81 | size_t index; 82 | 83 | if (set->nodes == SS_UNINITIALIZED) { 84 | return SS_NOT_FOUND; 85 | } 86 | 87 | index = _ss_hash(set, key); 88 | node = set->nodes[index]; 89 | 90 | for (previous = NULL; node; previous = node, node = node->next) { 91 | if (node->key == key) { 92 | if (previous) { 93 | previous->next = node->next; 94 | } else { 95 | set->nodes[index] = node->next; 96 | } 97 | 98 | free(node); 99 | 100 | if (--set->size == set->threshold / 4) { 101 | _ss_resize(set); 102 | } 103 | 104 | return SS_OK; 105 | } 106 | } 107 | 108 | return SS_NOT_FOUND; 109 | } 110 | 111 | void ss_clear(StringSet* set) { 112 | if (set->nodes == SS_UNINITIALIZED) return; 113 | ss_destroy(set); 114 | _ss_allocate(set, SS_MINIMUM_CAPACITY); 115 | set->size = 0; 116 | } 117 | 118 | bool ss_is_empty(StringSet* set) { 119 | return set->size == 0; 120 | } 121 | 122 | bool ss_is_initialized(StringSet* set) { 123 | return set->nodes != SS_UNINITIALIZED; 124 | } 125 | 126 | /***** PRIVATE *****/ 127 | 128 | bool _ss_equals(String first, String second) { 129 | return strcmp(first, second) == 0; 130 | } 131 | 132 | void _ss_create_if_necessary(StringSet* set) { 133 | assert(set != NULL); 134 | if (set->nodes == SS_UNINITIALIZED) { 135 | ss_setup(set, 0); 136 | } 137 | } 138 | 139 | void _ss_allocate(StringSet* set, size_t capacity) { 140 | size_t bytes; 141 | bytes = sizeof(Node*) * capacity; 142 | set->nodes = (Node**)malloc(bytes); 143 | memset(set->nodes, 0, bytes); 144 | 145 | set->capacity = capacity; 146 | set->threshold = capacity * SS_LOAD_FACTOR; 147 | } 148 | 149 | Node* _ss_create_node(String key, Node* next) { 150 | // Allocate the size of the node up to the flexible array member 151 | // and then also the node itself. This is cool, because only need 152 | // to delete the node and not the key separately (if we used a char* 153 | // instead of a flexible array). Also, cache lookup should be improved. 154 | Node* node = (Node*)malloc(sizeof(Node) + strlen(key)); 155 | 156 | strcpy(node->key, key); 157 | node->next = next; 158 | 159 | return node; 160 | } 161 | 162 | size_t _ss_hash(StringSet* set, String key) { 163 | // djb2 string hashing algorithm 164 | // sstp://www.cse.yorku.ca/~oz/hash.ssml 165 | size_t hash; 166 | 167 | for (hash = 5381; *key != '\0'; ++key) { 168 | // (hash << 5) + hash = hash * 33 169 | hash = ((hash << 5) + hash) ^ *key; 170 | } 171 | 172 | return hash % set->capacity; 173 | } 174 | 175 | void _ss_resize(StringSet* set) { 176 | size_t old_capacity = set->capacity; 177 | size_t new_capacity = set->size * 2; 178 | 179 | if (new_capacity < SS_MINIMUM_CAPACITY) return; 180 | 181 | Node** old = set->nodes; 182 | 183 | _ss_allocate(set, new_capacity); 184 | _ss_rehash(set, old, old_capacity); 185 | 186 | free(old); 187 | } 188 | 189 | void _ss_rehash(StringSet* set, Node** old, size_t old_capacity) { 190 | Node* node; 191 | Node* next; 192 | size_t new_index; 193 | size_t i; 194 | 195 | for (i = 0; i < old_capacity; ++i) { 196 | for (node = old[i]; node;) { 197 | next = node->next; 198 | 199 | new_index = _ss_hash(set, node->key); 200 | node->next = set->nodes[new_index]; 201 | set->nodes[new_index] = node; 202 | 203 | node = next; 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /source/tssx/timeouts.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "tssx/timeouts.h" 4 | 5 | const Timeouts DEFAULT_TIMEOUTS = DEFAULT_TIMEOUTS_INITIALIZER; 6 | -------------------------------------------------------------------------------- /source/tssx/try.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "tssx/buffer.h" 4 | 5 | int main(int argc, const char* argv[]) { 6 | int size = 1000000; 7 | void* memory = malloc(sizeof(Buffer) + size); 8 | int data; 9 | 10 | Buffer* buffer = create_buffer(memory, size, &DEFAULT_TIMEOUTS); 11 | 12 | cycle_t sum = 0; 13 | cycle_t n = 100000; 14 | for (int i = 0; i < n; ++i) { 15 | cycle_t start = _now(); 16 | buffer_write(buffer, &data, sizeof data); 17 | // buffer_read(buffer, &data, sizeof data); 18 | sum += _now() - start; 19 | } 20 | 21 | free(memory); 22 | } 23 | -------------------------------------------------------------------------------- /source/utility/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## SOURCES 3 | ########################################################### 4 | 5 | set(TSSX_UTILITY_SOURCES 6 | ${CMAKE_CURRENT_SOURCE_DIR}/utility.c 7 | ${CMAKE_CURRENT_SOURCE_DIR}/benchmarks.c 8 | ${CMAKE_CURRENT_SOURCE_DIR}/signals.c 9 | ${CMAKE_CURRENT_SOURCE_DIR}/arguments.c 10 | ${CMAKE_CURRENT_SOURCE_DIR}/process.c 11 | ${CMAKE_CURRENT_SOURCE_DIR}/sockets.c 12 | ${CMAKE_CURRENT_SOURCE_DIR}/parent.c 13 | ) 14 | 15 | ########################################################### 16 | ## TARGETS 17 | ########################################################### 18 | 19 | add_library(tssx-utility STATIC ${TSSX_UTILITY_SOURCES}) 20 | target_compile_options(tssx-utility PUBLIC -fPIC) 21 | -------------------------------------------------------------------------------- /source/utility/arguments.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "utility/arguments.h" 7 | 8 | #define true 1 9 | #define false 0 10 | 11 | void print_usage() { 12 | printf( 13 | "Usage: fifos " 14 | "-s/--size " 15 | "-c/--count " 16 | "\n"); 17 | exit(EXIT_FAILURE); 18 | } 19 | 20 | void parse_arguments(Arguments *arguments, int argc, char *argv[]) { 21 | // For getopt long options 22 | int long_index = 0; 23 | // For getopt chars 24 | int option; 25 | 26 | // Reset the option index to 1 if it 27 | // was modified before (e.g. in check_flag) 28 | optind = 0; 29 | 30 | // Default values 31 | arguments->size = DEFAULT_MESSAGE_SIZE; 32 | arguments->count = 1000; 33 | 34 | // Command line arguments 35 | // clang-format off 36 | static struct option long_options[] = { 37 | {"size", required_argument, NULL, 's'}, 38 | {"count", required_argument, NULL, 'c'}, 39 | {0, 0, 0, 0} 40 | }; 41 | // clang-format on 42 | 43 | while (true) { 44 | option = getopt_long(argc, argv, "+:s:c:", long_options, &long_index); 45 | 46 | switch (option) { 47 | case -1: return; 48 | case 's': arguments->size = atoi(optarg); break; 49 | case 'c': arguments->count = atoi(optarg); break; 50 | default: continue; 51 | } 52 | } 53 | } 54 | 55 | int check_flag(const char *flag, int argc, char *argv[]) { 56 | // For getopt long options 57 | int index = 0; 58 | // The char returned by getopt() 59 | int option; 60 | // Setting the first character to be a plus 61 | // prevents getopt() from reordering the vector, 62 | // setting the second character to be a colon 63 | // prevents getopt() from printing an error 64 | // message when it encounters invalid options 65 | char short_flag[3] = {'+', ':', flag[0]}; 66 | 67 | // Reset getopt index 68 | optind = 0; 69 | 70 | // clang-format off 71 | struct option long_options [2] = { 72 | {flag, no_argument, NULL, flag[0]}, 73 | {0, 0, 0, 0} 74 | }; 75 | // clang-format on 76 | 77 | while (true) { 78 | option = getopt_long(argc, argv, short_flag, long_options, &index); 79 | 80 | if (option == flag[0]) { 81 | return true; 82 | } else if (option == -1) { 83 | break; 84 | } 85 | } 86 | 87 | return false; 88 | } 89 | -------------------------------------------------------------------------------- /source/utility/benchmarks.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "utility/arguments.h" 9 | #include "utility/benchmarks.h" 10 | 11 | bench_t now() { 12 | #ifdef __MACH__ 13 | return ((double)clock()) / CLOCKS_PER_SEC * 1e9; 14 | #else 15 | struct timespec ts; 16 | timespec_get(&ts, TIME_UTC); 17 | 18 | return ts.tv_sec * 1e9 + ts.tv_nsec; 19 | 20 | #endif 21 | } 22 | 23 | void setup_benchmarks(Benchmarks* bench) { 24 | bench->minimum = INT32_MAX; 25 | bench->maximum = 0; 26 | bench->sum = 0; 27 | bench->squared_sum = 0; 28 | bench->total_start = now(); 29 | } 30 | 31 | void benchmark(Benchmarks* bench) { 32 | const bench_t time = now() - bench->single_start; 33 | 34 | if (time < bench->minimum) { 35 | bench->minimum = time; 36 | } 37 | 38 | if (time > bench->maximum) { 39 | bench->maximum = time; 40 | } 41 | 42 | bench->sum += time; 43 | bench->squared_sum += (time * time); 44 | } 45 | 46 | void evaluate(Benchmarks* bench, Arguments* args) { 47 | assert(args->count > 0); 48 | const bench_t total_time = now() - bench->total_start; 49 | const double average = ((double)bench->sum) / args->count; 50 | 51 | double sigma = bench->squared_sum / args->count; 52 | sigma = sqrt(sigma - (average * average)); 53 | 54 | int messageRate = (int)(args->count / (total_time / 1e9)); 55 | 56 | printf("\n============ RESULTS =============\n"); 57 | printf("Message size: %d\n", args->size); 58 | printf("Message count: %d\n", args->count); 59 | printf("Total duration: %.3f\tms\n", total_time / 1e6); 60 | printf("Average duration: %.3f\tus\n", average / 1000.0); 61 | printf("Minimum duration: %.3f\tus\n", bench->minimum / 1000.0); 62 | printf("Maximum duration: %.3f\tus\n", bench->maximum / 1000.0); 63 | printf("Standard deviation: %.3f\tus\n", sigma / 1000.0); 64 | printf("Message rate: %d\tmsg/s\n", messageRate); 65 | printf("EasyToPlot min %.3f us\n", bench->minimum / 1000.0); 66 | printf("==================================\n"); 67 | } 68 | -------------------------------------------------------------------------------- /source/utility/parent.c: -------------------------------------------------------------------------------- 1 | #include "utility/parent.h" 2 | #include "utility/arguments.h" 3 | #include "utility/process.h" 4 | #include "utility/signals.h" 5 | 6 | void setup_parent(char* name, int argc, char* argv[]) { 7 | if (check_flag("help", argc, argv)) { 8 | print_usage(); 9 | } 10 | setup_parent_signals(); 11 | start_children(name, argc, argv); 12 | } 13 | -------------------------------------------------------------------------------- /source/utility/process.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "utility/utility.h" 9 | 10 | #define BUILD_PATH "/build/source\0" 11 | 12 | char *find_build_path() { 13 | char *path = (char *)malloc(strlen(__FILE__) + strlen(BUILD_PATH)); 14 | char *right; 15 | char *left; 16 | 17 | strcpy(path, __FILE__); 18 | right = path + strlen(__FILE__); 19 | left = right - strlen("ipc-bench"); 20 | 21 | // Make the string-comparison expected O(N) 22 | // by only doing a string-comparison when 23 | // the first and last character match 24 | for (--right; left >= path; --left, --right) { 25 | if (*left == 'i' && *right == 'h') { 26 | if (strncmp("ipc-bench", left, 9) == 0) { 27 | break; 28 | } 29 | } 30 | } 31 | 32 | // ++ because right is on the 'h' 33 | strcpy(++right, BUILD_PATH); 34 | 35 | return path; 36 | } 37 | 38 | 39 | void start_process(char *argv[]) { 40 | // Will need to set the group id 41 | const pid_t parent_pid = getpid(); 42 | const pid_t pid = fork(); 43 | 44 | if (pid == 0) { 45 | // Set group id of the children so that we 46 | // can send around signals 47 | if (setpgid(pid, parent_pid) == -1) { 48 | throw("Could not set group id for child process"); 49 | } 50 | // Replace the current process with the command 51 | // we want to execute (child or server) 52 | // First argument is the command to call, 53 | // second is an array of arguments, where the 54 | // command path has to be included as well 55 | // (that's why argv[0] first) 56 | if (execv(argv[0], argv) == -1) { 57 | throw("Error opening child process"); 58 | } 59 | } 60 | } 61 | 62 | void copy_arguments(char *arguments[], int argc, char *argv[]) { 63 | int i; 64 | assert(argc < 8); 65 | for (i = 1; i < argc; ++i) { 66 | arguments[i] = argv[i]; 67 | } 68 | 69 | arguments[argc] = NULL; 70 | } 71 | 72 | void start_child(char *name, int argc, char *argv[]) { 73 | char *arguments[8] = {name}; 74 | copy_arguments(arguments, argc, argv); 75 | start_process(arguments); 76 | } 77 | 78 | void start_children(char *prefix, int argc, char *argv[]) { 79 | char server_name[100]; 80 | char client_name[100]; 81 | 82 | char *build_path = find_build_path(); 83 | 84 | // clang-format off 85 | sprintf( 86 | server_name, 87 | "%s/%s/%s-%s", 88 | build_path, 89 | prefix, 90 | prefix, 91 | "server" 92 | ); 93 | 94 | sprintf( 95 | client_name, 96 | "%s/%s/%s-%s", 97 | build_path, 98 | prefix, 99 | prefix, 100 | "client" 101 | ); 102 | // clang-format on 103 | 104 | start_child(server_name, argc, argv); 105 | start_child(client_name, argc, argv); 106 | 107 | free(build_path); 108 | } 109 | -------------------------------------------------------------------------------- /source/utility/signals.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "utility/signals.h" 6 | #include "utility/utility.h" 7 | 8 | void signal_handler(int signal_number) { 9 | } 10 | 11 | 12 | void setup_ignored_signals(struct sigaction *signal_action, int flags) { 13 | // Now ignore the other signals 14 | signal_action->sa_handler = signal_handler; 15 | 16 | // Ignore SIGUSR1 ? 17 | if (!(flags & BLOCK_USR1)) { 18 | // Set signal handler 19 | if (sigaction(SIGUSR1, signal_action, NULL)) { 20 | throw("Error registering SIGUSR1 signal handler for server"); 21 | } 22 | } 23 | 24 | // Ignore SIGUSR2 ? 25 | if (!(flags & BLOCK_USR2)) { 26 | // Set signal handler 27 | if (sigaction(SIGUSR2, signal_action, NULL)) { 28 | throw("Error registering SIGUSR2 signal handler for server"); 29 | } 30 | } 31 | } 32 | 33 | void setup_blocked_signals(struct sigaction *signal_action, int flags) { 34 | signal_action->sa_handler = SIG_DFL; 35 | 36 | // Block SIGUSR1 ? 37 | if (flags & BLOCK_USR1) { 38 | sigaddset(&signal_action->sa_mask, SIGUSR1); 39 | } 40 | 41 | // Block SIGUSR2 ? 42 | if (flags & BLOCK_USR2) { 43 | sigaddset(&signal_action->sa_mask, SIGUSR2); 44 | } 45 | 46 | // Change signal mask 47 | sigprocmask(SIG_BLOCK, &signal_action->sa_mask, NULL); 48 | } 49 | 50 | void setup_signals(struct sigaction *signal_action, int flags) { 51 | // Let system calls restart when it 52 | // was interrupted by a signal 53 | signal_action->sa_flags = SA_RESTART; 54 | 55 | // Clear all flags 56 | sigemptyset(&signal_action->sa_mask); 57 | 58 | setup_ignored_signals(signal_action, flags); 59 | 60 | // Clear all flags 61 | sigemptyset(&signal_action->sa_mask); 62 | 63 | setup_blocked_signals(signal_action, flags); 64 | } 65 | 66 | void setup_parent_signals() { 67 | struct sigaction signal_action; 68 | setup_signals(&signal_action, IGNORE_USR1 | IGNORE_USR2); 69 | } 70 | 71 | void setup_server_signals(struct sigaction *signal_action) { 72 | setup_signals(signal_action, BLOCK_USR1 | IGNORE_USR2); 73 | usleep(1000); 74 | } 75 | 76 | void setup_client_signals(struct sigaction *signal_action) { 77 | setup_signals(signal_action, IGNORE_USR1 | BLOCK_USR2); 78 | usleep(1000); 79 | } 80 | 81 | void notify_server() { 82 | kill(0, SIGUSR1); 83 | } 84 | 85 | void notify_client() { 86 | kill(0, SIGUSR2); 87 | } 88 | 89 | void wait_for_signal(struct sigaction *signal_action) { 90 | int signal_number; 91 | sigwait(&(signal_action->sa_mask), &signal_number); 92 | } 93 | 94 | void client_once(int operation) { 95 | struct sigaction signal_action; 96 | setup_client_signals(&signal_action); 97 | if (operation == WAIT) { 98 | wait_for_signal(&signal_action); 99 | } else { 100 | notify_server(); 101 | } 102 | } 103 | 104 | void server_once(int operation) { 105 | struct sigaction signal_action; 106 | setup_server_signals(&signal_action); 107 | if (operation == WAIT) { 108 | wait_for_signal(&signal_action); 109 | } else { 110 | notify_client(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /source/utility/sockets.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "utility/sockets.h" 8 | #include "utility/utility.h" 9 | 10 | typedef struct timeval timeval; 11 | 12 | int socket_buffer_size(int socket_fd, Direction direction) { 13 | int return_code; 14 | int buffer_size; 15 | socklen_t value_size = sizeof buffer_size; 16 | 17 | // clang-format off 18 | return_code = getsockopt( 19 | socket_fd, 20 | SOL_SOCKET, 21 | (direction == SEND) ? SO_SNDBUF : SO_RCVBUF, 22 | &buffer_size, 23 | &value_size 24 | ); 25 | // clang-format on 26 | 27 | if (return_code == -1) { 28 | throw("Error getting buffer size"); 29 | } 30 | 31 | return buffer_size; 32 | } 33 | 34 | timeval socket_timeout(int socket_fd, Direction direction) { 35 | int return_code; 36 | timeval timeout; 37 | socklen_t value_size = sizeof timeout; 38 | 39 | // clang-format off 40 | return_code = getsockopt( 41 | socket_fd, 42 | SOL_SOCKET, 43 | (direction == SEND) ? SO_SNDTIMEO : SO_RCVTIMEO, 44 | &timeout, 45 | &value_size 46 | ); 47 | // clang-format on 48 | 49 | if (return_code == -1) { 50 | throw("Error getting socket timeout"); 51 | } 52 | 53 | return timeout; 54 | } 55 | 56 | double socket_timeout_seconds(int socket_fd, Direction direction) { 57 | timeval timeout = socket_timeout(socket_fd, direction); 58 | return timeout.tv_sec + (timeout.tv_usec / 1e6); 59 | } 60 | 61 | void set_socket_buffer_size(int socket_fd, Direction direction) { 62 | int buffer_size = BUFFER_SIZE; 63 | 64 | // set/getsockopt is the one-stop-shop for all socket options. 65 | // With it, you can get/set a variety of options for a given socket. 66 | // Arguments: 67 | // 1. The socket file-fd. 68 | // 2. The level of the socket, should be SOL_SOCKET. 69 | // 3. The option you want to get/set. 70 | // 4. The address of a value you want to get/set into. 71 | // 5. The memory-size of said value. 72 | // clang-format off 73 | 74 | // Set the sockets send-buffer-size (SNDBUF) 75 | int return_code = setsockopt( 76 | socket_fd, 77 | SOL_SOCKET, 78 | (direction == SEND) ? SO_SNDBUF : SO_RCVBUF, 79 | &buffer_size, 80 | sizeof buffer_size 81 | ); 82 | // clang-format on 83 | 84 | if (return_code == -1) { 85 | throw("Error setting socket buffer size"); 86 | } 87 | } 88 | 89 | void set_socket_both_buffer_sizes(int socket_fd) { 90 | // clang-format off 91 | set_socket_buffer_size( 92 | socket_fd, 93 | SEND 94 | ); 95 | 96 | set_socket_buffer_size( 97 | socket_fd, 98 | RECEIVE 99 | ); 100 | // clang-format on 101 | } 102 | 103 | void set_socket_timeout(int socket_fd, timeval* timeout, Direction direction) { 104 | // clang-format off 105 | int return_code = setsockopt( 106 | socket_fd, 107 | SOL_SOCKET, 108 | (direction == SEND) ? SO_SNDTIMEO : SO_RCVTIMEO, 109 | timeout, 110 | sizeof *timeout 111 | ); 112 | // clang-format on 113 | 114 | if (return_code == -1) { 115 | throw("Error setting blocking timeout"); 116 | } 117 | } 118 | 119 | void set_socket_both_timeouts(int socket_fd, int seconds, int microseconds) { 120 | struct timeval timeout = {seconds, microseconds}; 121 | 122 | set_socket_timeout(socket_fd, &timeout, SEND); 123 | set_socket_timeout(socket_fd, &timeout, RECEIVE); 124 | } 125 | 126 | 127 | int set_io_flag(int socket_fd, int flag) { 128 | int old_flags; 129 | 130 | // Get the old flags, because we must bitwise-OR our flag to add it 131 | // fnctl takes as arguments: 132 | // 1. The file fd to modify 133 | // 2. The command (e.g. F_GETFL, F_SETFL, ...) 134 | // 3. Arguments to that command (variadic) 135 | // For F_GETFL, the arguments are ignored (that's why we pass 0) 136 | if ((old_flags = fcntl(socket_fd, F_GETFL, 0)) == -1) { 137 | return -1; 138 | } 139 | 140 | if (fcntl(socket_fd, F_SETFL, old_flags | flag)) { 141 | return -1; 142 | } 143 | 144 | return 0; 145 | } 146 | 147 | int receive(int connection, void* buffer, int size, int busy_waiting) { 148 | if (busy_waiting) { 149 | while (recv(connection, buffer, size, 0) < size) { 150 | if (errno != EAGAIN) return -1; 151 | } 152 | } else if (recv(connection, buffer, size, 0) < size) { 153 | return -1; 154 | } 155 | 156 | return 0; 157 | } 158 | 159 | int get_socket_flags(int socket_fd) { 160 | int flags; 161 | if ((flags = fcntl(socket_fd, F_GETFL)) == -1) { 162 | throw("Error retrieving flags"); 163 | } 164 | 165 | return flags; 166 | } 167 | 168 | void set_socket_flags(int socket_fd, int flags) { 169 | if ((flags = fcntl(socket_fd, F_SETFL, flags)) == -1) { 170 | throw("Error setting flags"); 171 | } 172 | } 173 | 174 | int set_socket_non_blocking(int socket_fd) { 175 | int flags; 176 | 177 | flags = get_socket_flags(socket_fd); 178 | flags |= O_NONBLOCK; 179 | set_socket_flags(socket_fd, flags); 180 | 181 | return flags; 182 | } 183 | 184 | int unset_socket_non_blocking(int socket_fd) { 185 | int flags; 186 | 187 | flags = get_socket_flags(socket_fd); 188 | // This function is supposed to return the old flags 189 | set_socket_flags(socket_fd, flags & ~O_NONBLOCK); 190 | 191 | return flags; 192 | } 193 | 194 | bool socket_is_non_blocking(int socket_fd) { 195 | return get_socket_flags(socket_fd) & O_NONBLOCK; 196 | } 197 | -------------------------------------------------------------------------------- /source/utility/utility.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define __USE_GNU 9 | #include 10 | #include 11 | 12 | #include "utility/utility.h" 13 | 14 | void throw(const char* message) { 15 | perror(message); 16 | exit(EXIT_FAILURE); 17 | } 18 | 19 | void terminate(const char* message) { 20 | fputs(message, stderr); 21 | exit(EXIT_FAILURE); 22 | } 23 | 24 | void print_error(const char* message) { 25 | fprintf(stderr, "%s\n", message); 26 | } 27 | 28 | void warn(const char* message) { 29 | fprintf(stderr, "\033[33mWarning\033[0m: %s\n", message); 30 | } 31 | 32 | int generate_key(const char* path) { 33 | // Generate a random key from the given file path 34 | // (inode etc.) plus the arbitrary character 35 | return ftok(path, 'X'); 36 | } 37 | 38 | void nsleep(int nanoseconds) { 39 | struct timespec time = {0, nanoseconds}; 40 | if (nanosleep(&time, NULL) == -1) { 41 | throw("Sleep was interrupted"); 42 | } 43 | } 44 | 45 | int current_milliseconds() { 46 | struct timeval current_time; 47 | 48 | if (gettimeofday(¤t_time, NULL) == -1) { 49 | throw("Error getting time"); 50 | } 51 | 52 | return timeval_to_milliseconds(¤t_time); 53 | } 54 | 55 | int timeval_to_milliseconds(const struct timeval* time) { 56 | int milliseconds; 57 | 58 | assert(time != NULL); 59 | 60 | milliseconds = time->tv_sec * 1000; 61 | milliseconds += time->tv_usec / 1000; 62 | 63 | return milliseconds; 64 | } 65 | 66 | void timespec_to_timeval(const struct timespec* source, 67 | struct timeval* destination) { 68 | assert(destination != NULL); 69 | 70 | if (source == NULL) return; 71 | 72 | destination->tv_sec = source->tv_sec; 73 | destination->tv_usec = source->tv_nsec / 1000; 74 | } 75 | 76 | void pin_thread(int where) { 77 | #ifdef __linux__ 78 | // Doesn't work on OS X right now 79 | int j; 80 | cpu_set_t cpuset; 81 | pthread_t thread; 82 | thread = pthread_self(); 83 | CPU_ZERO(&cpuset); 84 | CPU_SET(where, &cpuset); 85 | pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset); 86 | int s = pthread_getaffinity_np(thread, sizeof(cpu_set_t), &cpuset); 87 | if (s != 0) { 88 | fprintf(stderr, "error: pthread_getaffinity_np"); 89 | exit(-1); 90 | } 91 | #endif 92 | } 93 | -------------------------------------------------------------------------------- /try/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## SOURCES 3 | ########################################################### 4 | 5 | set(TSSX_TRY_COMMON_SOURCES 6 | ${CMAKE_CURRENT_SOURCE_DIR}/try-common.c 7 | ${CMAKE_CURRENT_SOURCE_DIR}/try-select.c 8 | ${CMAKE_CURRENT_SOURCE_DIR}/try-poll.c 9 | ) 10 | 11 | if (NOT APPLE) 12 | set(TSSX_TRY_COMMON_SOURCES 13 | ${TSSX_TRY_COMMON_SOURCES} 14 | ${CMAKE_CURRENT_SOURCE_DIR}/try-epoll.c 15 | ) 16 | endif() 17 | 18 | set(TSSX_TRY_SERVER_SOURCES 19 | ${TSSX_TRY_COMMON_SOURCES} 20 | ${CMAKE_CURRENT_SOURCE_DIR}/try-server.c 21 | ) 22 | 23 | set(TSSX_TRY_CLIENT_SOURCES 24 | ${TSSX_TRY_COMMON_SOURCES} 25 | ${CMAKE_CURRENT_SOURCE_DIR}/try-client.c 26 | ) 27 | 28 | ########################################################### 29 | ## TARGET 30 | ########################################################### 31 | 32 | add_executable(try-server ${TSSX_TRY_SERVER_SOURCES}) 33 | add_executable(try-client ${TSSX_TRY_CLIENT_SOURCES}) 34 | -------------------------------------------------------------------------------- /try/client: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goldsborough/tssx/93088d1b0c7fb1b8fe1aa7ae9d4eb50ec349eac2/try/client -------------------------------------------------------------------------------- /try/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goldsborough/tssx/93088d1b0c7fb1b8fe1aa7ae9d4eb50ec349eac2/try/server -------------------------------------------------------------------------------- /try/try-client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "try-common.h" 5 | 6 | void connection_loop(int client_socket) { 7 | char buffer[MESSAGE_SIZE]; 8 | memset(buffer, '6', sizeof buffer); 9 | 10 | while (true) { 11 | int amount_read; 12 | 13 | if (write(client_socket, buffer, MESSAGE_SIZE) < MESSAGE_SIZE) { 14 | throw("Error writing on client side"); 15 | } 16 | 17 | if ((amount_read = read(client_socket, buffer, MESSAGE_SIZE)) == 0) { 18 | return; 19 | } else if (amount_read < MESSAGE_SIZE) { 20 | throw("Error writing on client side"); 21 | } 22 | 23 | printf(". "); 24 | fflush(stdout); 25 | sleep(1); 26 | } 27 | } 28 | 29 | int request_client_socket() { 30 | int client_socket; 31 | 32 | if ((client_socket = socket(PF_LOCAL, SOCK_STREAM, 0)) == ERROR) { 33 | throw("Error getting client socket"); 34 | } 35 | 36 | set_cloexec_flag(client_socket); 37 | 38 | return client_socket; 39 | } 40 | 41 | void setup_client_socket(int client_socket) { 42 | struct sockaddr_un socket_address; 43 | struct sockaddr *raw_address; 44 | 45 | socket_address.sun_family = AF_LOCAL; 46 | strcpy(socket_address.sun_path, SOCKET_PATH); 47 | raw_address = (struct sockaddr *)&socket_address; 48 | 49 | if (connect(client_socket, raw_address, SUN_LEN(&socket_address)) == ERROR) { 50 | throw("Error connecting to socket address"); 51 | } 52 | } 53 | 54 | int main(int argc, const char *argv[]) { 55 | int client_socket; 56 | 57 | client_socket = request_client_socket(); 58 | setup_client_socket(client_socket); 59 | connection_loop(client_socket); 60 | 61 | close(client_socket); 62 | 63 | return EXIT_SUCCESS; 64 | } 65 | -------------------------------------------------------------------------------- /try/try-common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "try-common.h" 5 | 6 | void throw(const char *message) { 7 | perror(message); 8 | exit(EXIT_FAILURE); 9 | } 10 | 11 | void die(const char *message) { 12 | print_error(message); 13 | exit(EXIT_FAILURE); 14 | } 15 | 16 | void print_error(const char *message) { 17 | fputs(message, stderr); 18 | } 19 | 20 | void setup_timeout(struct timeval *timeout) { 21 | timeout->tv_sec = TIMEOUT_SECONDS; 22 | timeout->tv_usec = 0; 23 | } 24 | 25 | void set_cloexec_flag(int socket_fd) { 26 | // Don't leak FDs 27 | if (fcntl(socket_fd, F_SETFD, FD_CLOEXEC) == ERROR) { 28 | throw("Error using fcntl for CLOEXEC flag"); 29 | } 30 | } 31 | 32 | void set_nonblocking(int socket_fd) { 33 | int flags; 34 | 35 | if ((flags = fcntl(socket_fd, F_GETFL)) == ERROR) { 36 | throw("Error getting socket flags to unblock\n"); 37 | } 38 | 39 | flags |= O_NONBLOCK; 40 | if (fcntl(socket_fd, F_SETFL, flags) == ERROR) { 41 | throw("Error setting socket flags to unblock\n"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /try/try-common.h: -------------------------------------------------------------------------------- 1 | #ifndef TRY_COMMON_H 2 | #define TRY_COMMON_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /************************ DEFINITIONS ************************/ 15 | 16 | #define ERROR -1 17 | #define SUCCESS 0 18 | #define TIMEOUT 20000// milliseconds 19 | 20 | #define SOCKET_PATH "/tmp/try-socket" 21 | #define MESSAGE_SIZE 4096 22 | #define DEFAULT_METHOD SELECT 23 | #define BACKLOG 10 24 | #define TIMEOUT_SECONDS 20 25 | 26 | typedef enum Method { SELECT, POLL, EPOLL } Method; 27 | 28 | struct timeval; 29 | 30 | /************************ INTERFACE ************************/ 31 | 32 | void throw(const char *message); 33 | void die(const char *message); 34 | void print_error(const char *message); 35 | void setup_timeout(struct timeval *timeout); 36 | void set_cloexec_flag(int socket_fd); 37 | void set_nonblocking(int socket_fd); 38 | 39 | #endif /* TRY_COMMON_H */ 40 | -------------------------------------------------------------------------------- /try/try-epoll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "try-common.h" 5 | #include "try-epoll.h" 6 | 7 | /************************ INTERFACE ************************/ 8 | 9 | void epoll_loop(int server_socket) { 10 | struct epoll_event events[MAXIMUM_NUMBER_OF_CONNECTIONS]; 11 | int epfd; 12 | int timeout; 13 | 14 | sigset_t sigmask; 15 | sigemptyset(&sigmask); 16 | sigaddset(&sigmask, SIGINT); 17 | 18 | timeout = TIMEOUT; 19 | epfd = _request_epoll_fd(); 20 | _register_epoll_socket(epfd, server_socket); 21 | 22 | while (true) { 23 | int number_of_events; 24 | 25 | number_of_events = epoll_wait(epfd, events, sizeof events, timeout); 26 | switch (number_of_events) { 27 | case ERROR: throw("Error on epoll"); 28 | case 0: die("Timeout on epoll\n"); 29 | } 30 | 31 | _handle_epoll_requests(epfd, server_socket, events, number_of_events); 32 | } 33 | 34 | close(epfd); 35 | } 36 | 37 | int _request_epoll_fd() { 38 | int epfd; 39 | 40 | if ((epfd = epoll_create1(EPOLL_CLOEXEC)) == ERROR) { 41 | throw("Error requesting epoll master file descriptor"); 42 | } 43 | 44 | return epfd; 45 | } 46 | 47 | void _register_epoll_socket(int epfd, int socket_fd) { 48 | struct epoll_event event; 49 | 50 | event.events = EPOLLIN; 51 | event.data.fd = socket_fd; 52 | 53 | if (epoll_ctl(epfd, EPOLL_CTL_ADD, socket_fd, &event) == ERROR) { 54 | throw("Error adding socket to epoll instance"); 55 | } 56 | } 57 | 58 | void _remove_epoll_socket(int epfd, int socket_fd) { 59 | if (epoll_ctl(epfd, EPOLL_CTL_DEL, socket_fd, NULL) == ERROR) { 60 | throw("Error removing socket from epoll instance"); 61 | } 62 | 63 | if (close(socket_fd) == ERROR) { 64 | throw("Error closing socket descriptor"); 65 | } 66 | 67 | printf("Death to client %d\n", socket_fd); 68 | } 69 | 70 | void _accept_epoll_connections(int epfd, int server_socket) { 71 | int client_socket; 72 | 73 | if ((client_socket = accept(server_socket, NULL, NULL)) == ERROR) { 74 | throw("Error accepting connection on server side"); 75 | } 76 | 77 | _register_epoll_socket(epfd, client_socket); 78 | 79 | printf("New connection: %d\n", client_socket); 80 | } 81 | 82 | void _handle_epoll_requests(int epfd, 83 | int server_socket, 84 | struct epoll_event* events, 85 | size_t number_of_events) { 86 | char buffer[MESSAGE_SIZE]; 87 | assert(number_of_events > 0); 88 | 89 | for (size_t index = 0; index < number_of_events; ++index) { 90 | int fd = events[index].data.fd; 91 | 92 | assert(events[index].events & EPOLLIN); 93 | 94 | if (fd == server_socket) { 95 | _accept_epoll_connections(epfd, server_socket); 96 | } else { 97 | int amount_read; 98 | 99 | if ((amount_read = read(fd, buffer, MESSAGE_SIZE)) == 0) { 100 | _remove_epoll_socket(epfd, fd); 101 | } else if (amount_read < MESSAGE_SIZE) { 102 | throw("Error reading on server side"); 103 | } else { 104 | if (write(fd, buffer, MESSAGE_SIZE) < MESSAGE_SIZE) { 105 | throw("Error writing on server side"); 106 | } 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /try/try-epoll.h: -------------------------------------------------------------------------------- 1 | #ifndef TRY_EPOLL_H 2 | #define TRY_EPOLL_H 3 | 4 | #include 5 | 6 | /************************ DEFINITIONS ************************/ 7 | 8 | #define MAXIMUM_NUMBER_OF_CONNECTIONS 1000 9 | 10 | struct epoll_event; 11 | 12 | /************************ INTERFACE ************************/ 13 | 14 | void epoll_loop(int server_socket); 15 | 16 | /************************ PRIVATE ************************/ 17 | 18 | int _request_epoll_fd(); 19 | 20 | void _register_epoll_socket(int epfd, int socket_fd); 21 | 22 | void _remove_epoll_socket(int epfd, int socket_fd); 23 | 24 | void _accept_epoll_connections(int epfd, int server_socket); 25 | 26 | void _handle_epoll_requests(int epfd, 27 | int server_socket, 28 | struct epoll_event* events, 29 | size_t number_of_events); 30 | 31 | #endif /* TRY_EPOLL_H */ 32 | -------------------------------------------------------------------------------- /try/try-poll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "try-common.h" 5 | #include "try-poll.h" 6 | 7 | /************************ INTERFACE ************************/ 8 | 9 | void poll_loop(int server_socket) { 10 | struct pollfd poll_entries[MAXIMUM_NUMBER_OF_CONNECTIONS]; 11 | size_t number_of_connections; 12 | 13 | _setup_server_polling(poll_entries, server_socket); 14 | // The listener socket only (right now) 15 | number_of_connections = 1; 16 | 17 | while (true) { 18 | switch (poll(poll_entries, number_of_connections, TIMEOUT)) { 19 | case ERROR: throw("Error on poll"); break; 20 | case 0: die("Timeout on poll\n"); break; 21 | } 22 | _accept_poll_connections(poll_entries, 23 | server_socket, 24 | &number_of_connections); 25 | _handle_poll_requests(poll_entries, &number_of_connections); 26 | } 27 | } 28 | 29 | void _setup_server_polling(struct pollfd* poll_entries, int server_socket) { 30 | poll_entries[0].fd = server_socket; 31 | poll_entries[0].events = POLLIN; 32 | } 33 | 34 | void _accept_poll_connections(struct pollfd* poll_entries, 35 | int server_socket, 36 | size_t* number_of_connections) { 37 | int client_socket; 38 | if (!(poll_entries[0].revents & POLLIN)) return; 39 | 40 | if ((client_socket = accept(server_socket, NULL, NULL)) == ERROR) { 41 | throw("Error accepting connection on server side"); 42 | } 43 | 44 | set_nonblocking(client_socket); 45 | 46 | _setup_client_polling(poll_entries, client_socket, *number_of_connections); 47 | 48 | assert(*number_of_connections < MAXIMUM_NUMBER_OF_CONNECTIONS); 49 | ++(*number_of_connections); 50 | 51 | printf("New connection: %d\n", client_socket); 52 | } 53 | 54 | void _setup_client_polling(struct pollfd* poll_entries, 55 | int client_socket, 56 | int number_of_connections) { 57 | poll_entries[number_of_connections].fd = client_socket; 58 | // Listen for read and hangup events (connection lost) 59 | poll_entries[number_of_connections].events = POLLIN | POLLHUP; 60 | poll_entries[number_of_connections].revents = 0; 61 | } 62 | 63 | void _handle_poll_requests(struct pollfd* poll_entries, 64 | size_t* number_of_connections) { 65 | char buffer[MESSAGE_SIZE]; 66 | int amount_read; 67 | 68 | // The first entry is the listener socket 69 | for (size_t index = 1; index < *number_of_connections; /* in loop */) { 70 | if (poll_entries[index].revents & POLLHUP) { 71 | _handle_poll_disconnect(poll_entries, index, number_of_connections); 72 | continue; 73 | } 74 | 75 | if (!(poll_entries[index].revents & POLLIN)) { 76 | ++index; 77 | continue; 78 | } 79 | 80 | amount_read = read(poll_entries[index].fd, buffer, MESSAGE_SIZE); 81 | 82 | if (amount_read == 0) { 83 | _handle_poll_disconnect(poll_entries, index, number_of_connections); 84 | continue; 85 | } else if (amount_read < MESSAGE_SIZE) { 86 | throw("Error reading on server side"); 87 | } 88 | 89 | if (write(poll_entries[index].fd, buffer, MESSAGE_SIZE) < MESSAGE_SIZE) { 90 | throw("Error writing on server side"); 91 | } 92 | ++index; 93 | } 94 | } 95 | 96 | void _handle_poll_disconnect(struct pollfd* poll_entries, 97 | size_t index, 98 | size_t* number_of_connections) { 99 | assert(index < *number_of_connections); 100 | 101 | printf("Death to client %d\n", poll_entries[index].fd); 102 | 103 | close(poll_entries[index].fd); 104 | 105 | // Shift entries to the left 106 | for (++index; index < *number_of_connections; ++index) { 107 | poll_entries[index - 1] = poll_entries[index]; 108 | } 109 | 110 | --(*number_of_connections); 111 | poll_entries[*number_of_connections].fd = -1; 112 | } 113 | -------------------------------------------------------------------------------- /try/try-poll.h: -------------------------------------------------------------------------------- 1 | #ifndef TRY_POLL_H 2 | #define TRY_POLL_H 3 | 4 | #include 5 | 6 | /************************ DEFINITIONS ************************/ 7 | 8 | #define MAXIMUM_NUMBER_OF_CONNECTIONS 1000 9 | 10 | struct pollfd; 11 | 12 | /************************ INTERFACE ************************/ 13 | 14 | void poll_loop(int server_socket); 15 | 16 | /************************ PRIVATE ************************/ 17 | 18 | void _setup_server_polling(struct pollfd* poll_entries, int server_socket); 19 | void _setup_client_polling(struct pollfd* poll_entries, 20 | int client_socket, 21 | int number_of_connections); 22 | 23 | void _accept_poll_connections(struct pollfd* poll_entries, 24 | int server_socket, 25 | size_t* number_of_connections); 26 | void _handle_poll_requests(struct pollfd* poll_entries, 27 | size_t* number_of_connections); 28 | 29 | void _handle_poll_disconnect(struct pollfd* poll_entries, 30 | size_t index, 31 | size_t* number_of_connections); 32 | 33 | #endif /* TRY_POLL_H */ 34 | -------------------------------------------------------------------------------- /try/try-select.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "try-common.h" 5 | #include "try-select.h" 6 | 7 | /************************ INTERFACE ************************/ 8 | 9 | void select_loop(int server_socket) { 10 | int highest_fd; 11 | fd_set sockets; 12 | struct timeval timeout; 13 | ; 14 | 15 | setup_timeout(&timeout); 16 | FD_ZERO(&sockets); 17 | FD_SET(server_socket, &sockets); 18 | highest_fd = server_socket; 19 | 20 | while (true) { 21 | fd_set read_set = sockets; 22 | 23 | switch (select(highest_fd + 1, &read_set, NULL, NULL, &timeout)) { 24 | case ERROR: throw("Error on select"); break; 25 | case 0: die("Timeout on select\n"); break; 26 | } 27 | 28 | _accept_select_connections(&read_set, server_socket, &sockets, &highest_fd); 29 | _handle_select_requests(&read_set, &sockets, highest_fd, server_socket); 30 | } 31 | } 32 | 33 | /************************ PRIVATE ************************/ 34 | 35 | void _accept_select_connections(const fd_set* read_set, 36 | int server_socket, 37 | fd_set* sockets, 38 | int* highest_fd) { 39 | int client_socket; 40 | 41 | if (!FD_ISSET(server_socket, read_set)) return; 42 | 43 | if ((client_socket = accept(server_socket, NULL, NULL)) == ERROR) { 44 | throw("Error accepting connection on server side"); 45 | } 46 | 47 | set_nonblocking(client_socket); 48 | 49 | if (client_socket > *highest_fd) { 50 | *highest_fd = client_socket; 51 | } 52 | 53 | // Add to master set 54 | FD_SET(client_socket, sockets); 55 | 56 | printf("New connection %d\n", client_socket); 57 | } 58 | 59 | void _handle_select_requests(const fd_set* read_set, 60 | fd_set* sockets, 61 | int highest_fd, 62 | int server_socket) { 63 | int amount; 64 | char buffer[MESSAGE_SIZE]; 65 | memset(buffer, '6', sizeof buffer); 66 | 67 | for (int fd = 3; fd <= highest_fd; ++fd) { 68 | if (fd == server_socket) continue; 69 | if (!FD_ISSET(fd, read_set)) continue; 70 | 71 | // read()/recv() returns zero when the peer disconnects 72 | if ((amount = read(fd, buffer, MESSAGE_SIZE)) == 0) { 73 | FD_CLR(fd, sockets); 74 | close(fd); 75 | printf("%d has died ...\n", fd); 76 | } else if (amount < MESSAGE_SIZE) { 77 | throw("Error reading on server side"); 78 | } else { 79 | if (write(fd, buffer, MESSAGE_SIZE) < MESSAGE_SIZE) { 80 | throw("Error writing on server side"); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /try/try-select.h: -------------------------------------------------------------------------------- 1 | #ifndef TRY_SELECT_H 2 | #define TRY_SELECT_H 3 | 4 | #include 5 | 6 | /************************ DEFINITIONS ************************/ 7 | 8 | /************************ INTERFACE ************************/ 9 | 10 | void select_loop(int server_socket); 11 | 12 | /************************ PRIVATE ************************/ 13 | 14 | void _accept_select_connections(const fd_set* read_set, 15 | int server_socket, 16 | fd_set* sockets, 17 | int* highest_fd); 18 | 19 | void _handle_select_requests(const fd_set* read_set, 20 | fd_set* sockets, 21 | int highest_fd, 22 | int server_socket); 23 | 24 | #endif /* TRY_SELECT_H */ 25 | -------------------------------------------------------------------------------- /try/try-server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "try-common.h" 7 | #include "try-epoll.h" 8 | #include "try-poll.h" 9 | #include "try-select.h" 10 | 11 | void signal_handler(int signal_number) { 12 | assert(signal_number == SIGINT); 13 | printf("Shutting down ...\n"); 14 | exit(EXIT_SUCCESS); 15 | } 16 | 17 | void connection_loop(int server_socket, Method method) { 18 | switch (method) { 19 | case SELECT: select_loop(server_socket); break; 20 | case POLL: poll_loop(server_socket); break; 21 | #ifdef __APPLE__ 22 | case EPOLL: die("epoll not supported on OS X\n"); 23 | #else 24 | case EPOLL: epoll_loop(server_socket); break; 25 | #endif 26 | } 27 | } 28 | 29 | int request_server_socket() { 30 | int server_socket; 31 | 32 | // Get a socket file descriptor 33 | if ((server_socket = socket(PF_LOCAL, SOCK_STREAM, 0)) == ERROR) { 34 | throw("Error getting server socket"); 35 | } 36 | 37 | set_cloexec_flag(server_socket); 38 | set_nonblocking(server_socket); 39 | 40 | return server_socket; 41 | } 42 | 43 | void setup_server_socket(int server_socket) { 44 | struct sockaddr_un socket_address; 45 | struct sockaddr *raw_address; 46 | 47 | // Remove socket if it already exists 48 | // `remove` calls `unlink` if the path is a file, 49 | // else `rmdir` (so unlink is fine) 50 | unlink(SOCKET_PATH); 51 | 52 | socket_address.sun_family = AF_LOCAL; 53 | strcpy(socket_address.sun_path, SOCKET_PATH); 54 | 55 | raw_address = (struct sockaddr *)&socket_address; 56 | 57 | if (bind(server_socket, raw_address, SUN_LEN(&socket_address)) == ERROR) { 58 | throw("Error binding to socket address"); 59 | } 60 | 61 | // Enable listening 62 | if (listen(server_socket, SOMAXCONN) == ERROR) { 63 | throw("Error listening on server socket"); 64 | } 65 | } 66 | 67 | void install_signal_handler() { 68 | struct sigaction signal_action; 69 | 70 | signal_action.sa_handler = signal_handler; 71 | signal_action.sa_flags = SA_RESTART; 72 | sigemptyset(&signal_action.sa_mask); 73 | 74 | if (sigaction(SIGINT, &signal_action, NULL) == ERROR) { 75 | throw("Error installing signal handler"); 76 | } 77 | } 78 | 79 | Method parse_method(int argc, const char *argv[]) { 80 | if (argc == 1) { 81 | return DEFAULT_METHOD; 82 | } 83 | 84 | assert(argc == 2); 85 | if (strcmp(argv[1], "select") == 0) { 86 | return SELECT; 87 | } else if (strcmp(argv[1], "poll") == 0) { 88 | return POLL; 89 | } else if (strcmp(argv[1], "epoll") == 0) { 90 | return EPOLL; 91 | } else { 92 | fprintf(stderr, 93 | "Invalid method '%s' not in {select, poll, epoll}", 94 | argv[1]); 95 | exit(EXIT_FAILURE); 96 | } 97 | } 98 | 99 | int main(int argc, const char *argv[]) { 100 | int server_socket; 101 | Method method; 102 | 103 | method = parse_method(argc, argv); 104 | 105 | server_socket = request_server_socket(); 106 | setup_server_socket(server_socket); 107 | install_signal_handler(); 108 | 109 | connection_loop(server_socket, method); 110 | 111 | close(server_socket); 112 | 113 | return EXIT_SUCCESS; 114 | } 115 | --------------------------------------------------------------------------------