├── .clang-format ├── .github └── workflows │ └── ccpp.yml ├── .gitignore ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── benchmark ├── dragons.cpp ├── pubsub.cpp └── rpc.cpp ├── configure ├── include ├── catch.hpp └── shadesmar │ ├── concurrency │ ├── cond_var.h │ ├── lock.h │ ├── lockless_set.h │ ├── robust_lock.h │ ├── rw_lock.h │ └── scope.h │ ├── macros.h │ ├── memory │ ├── allocator.h │ ├── copier.h │ ├── double_allocator.h │ ├── dragons.h │ └── memory.h │ ├── pubsub │ ├── publisher.h │ ├── subscriber.h │ └── topic.h │ ├── rpc │ ├── channel.h │ ├── client.h │ └── server.h │ └── stats.h ├── release └── shadesmar.h ├── scripts ├── format.sh ├── install_deps.sh ├── lint.sh └── run_tests.sh ├── simul ├── README.md ├── requirements.txt └── simul.py └── test ├── allocator_test.cpp ├── dragons_test.cpp ├── pubsub_test.cpp └── rpc_test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | # Generated from CLion C/C++ Code Style settings 2 | BasedOnStyle: Google 3 | ColumnLimit: 79 -------------------------------------------------------------------------------- /.github/workflows/ccpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build-ubuntu: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | compiler: [g++, clang++] 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Install dependencies 14 | run: ./scripts/install_deps.sh 15 | - name: Configure 16 | env: 17 | CXX: ${{ matrix.compiler }} 18 | run: ./configure Debug 19 | - name: Build 20 | run: cmake --build build 21 | - name: Tests 22 | run: ./scripts/run_tests.sh 23 | 24 | lint-check: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v1 28 | - uses: actions/setup-python@v1 29 | - run: pip install cpplint 30 | - run: ./scripts/lint.sh 31 | 32 | single-header-check: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v1 36 | - name: Install dependencies 37 | run: | 38 | ./scripts/install_deps.sh 39 | pip3 install -r simul/requirements.txt 40 | - name: Generate single header 41 | run: python3 simul/simul.py 42 | - name: Configure 43 | run: | 44 | mkdir build 45 | cd build 46 | cmake -DCMAKE_BUILD_TYPE=Release -DSINGLE_H=ON .. 47 | - name: Build 48 | run: cmake --build build 49 | 50 | - uses: actions/upload-artifact@v2 51 | if: success() 52 | with: 53 | path: include/shadesmar.h 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-build-debug 3 | cmake-build-debug_ninja 4 | cmake-build-release 5 | cmake-build-release_ninja 6 | build 7 | include/shadesmar.h 8 | google_benchmark 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(shadesmar) 4 | 5 | 6 | set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG") 7 | set(CMAKE_CXX_FLAGS_DEBUG "-g -DDEBUG_BUILD") 8 | 9 | find_package(benchmark REQUIRED) 10 | 11 | set(CMAKE_CXX_STANDARD 14) 12 | 13 | include_directories(include) 14 | list(APPEND libs rt pthread stdc++fs) 15 | add_compile_options(-march=native) 16 | IF(SINGLE_H) 17 | add_definitions(-DSINGLE_HEADER) 18 | ENDIF(SINGLE_H) 19 | 20 | 21 | # TESTS 22 | add_executable(pubsub_test test/pubsub_test.cpp) 23 | target_link_libraries(pubsub_test ${libs}) 24 | 25 | add_executable(rpc_test test/rpc_test.cpp) 26 | target_link_libraries(rpc_test ${libs}) 27 | 28 | add_executable(dragons_test test/dragons_test.cpp) 29 | target_link_libraries(dragons_test ${libs}) 30 | 31 | add_executable(allocator_test test/allocator_test.cpp) 32 | target_link_libraries(allocator_test ${libs}) 33 | 34 | # BENCHMARKS 35 | add_executable(dragons_bench benchmark/dragons.cpp) 36 | target_link_libraries(dragons_bench ${libs} benchmark::benchmark) 37 | 38 | add_executable(pubsub_bench benchmark/pubsub.cpp) 39 | target_link_libraries(pubsub_bench ${libs}) 40 | 41 | add_executable(rpc_bench benchmark/rpc.cpp) 42 | target_link_libraries(rpc_bench ${libs}) 43 | 44 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at dheeraj98reddy@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Shadesmar 2 | 3 | ![C/C++ CI](https://github.com/Squadrick/shadesmar/workflows/C/C++%20CI/badge.svg) 4 | 5 | An IPC library that uses the system's shared memory to pass messages. Supports 6 | publish-subscribe and RPC. 7 | 8 | Requires: Linux and x86. 9 | Caution: Alpha software. 10 | 11 | #### Features 12 | 13 | * Multiple subscribers and publishers. 14 | 15 | * Uses a circular buffer to pass messages between processes. 16 | 17 | * Faster than using the network stack. High throughput, low latency for large 18 | messages. 19 | 20 | * Decentralized, without [resource starvation](https://squadrick.github.io/journal/ipc-locks.html). 21 | 22 | * Minimize or optimize data movement using custom copiers. 23 | 24 | #### Usage 25 | 26 | There's a single header file generated from the source code which can be 27 | found [here](https://raw.githubusercontent.com/Squadrick/shadesmar/master/release/shadesmar.h). 28 | 29 | If you want to generate the single header file yourself, clone the repo and run: 30 | ``` 31 | $ cd shadesmar 32 | $ python3 simul/simul.py 33 | ``` 34 | This will generate the file in `include/`. 35 | 36 | --- 37 | 38 | #### Publish-Subscribe 39 | 40 | Publisher: 41 | ```c++ 42 | #include 43 | 44 | int main() { 45 | shm::pubsub::Publisher p("topic_name"); 46 | const uint32_t data_size = 1024; 47 | void *data = malloc(data_size); 48 | 49 | for (int i = 0; i < 1000; ++i) { 50 | p.publish(data, data_size); 51 | } 52 | } 53 | ``` 54 | 55 | Subscriber: 56 | ```c++ 57 | #include 58 | 59 | void callback(shm::memory::Memblock *msg) { 60 | // `msg->ptr` to access `data` 61 | // `msg->size` to access `data_size` 62 | 63 | // The memory will be free'd at the end of this callback. 64 | // Copy to another memory location if you want to persist the data. 65 | // Alternatively, if you want to avoid the copy, you can call 66 | // `msg->no_delete()` which prevents the memory from being deleted 67 | // at the end of the callback. 68 | } 69 | 70 | int main() { 71 | shm::pubsub::Subscriber sub("topic_name", callback); 72 | 73 | // Using `spin_once` with a manual loop 74 | while(true) { 75 | sub.spin_once(); 76 | } 77 | // OR 78 | // Using `spin` 79 | sub.spin(); 80 | } 81 | ``` 82 | 83 | --- 84 | 85 | #### RPC 86 | 87 | Client: 88 | ```c++ 89 | #include 90 | 91 | int main() { 92 | Client client("channel_name"); 93 | shm::memory::Memblock req, resp; 94 | // Populate req. 95 | client.call(req, &resp); 96 | // Use resp here. 97 | 98 | // resp needs to be explicitly free'd. 99 | client.free_resp(&resp); 100 | } 101 | ``` 102 | 103 | Server: 104 | 105 | ```c++ 106 | #include 107 | 108 | bool callback(const shm::memory::Memblock &req, 109 | shm::memory::Memblock *resp) { 110 | // resp->ptr is a void ptr, resp->size is the size of the buffer. 111 | // You can allocate memory here, which can be free'd in the clean-up lambda. 112 | return true; 113 | } 114 | 115 | void clean_up(shm::memory::Memblock *resp) { 116 | // This function is called *after* the callback is finished. Any memory 117 | // allocated for the response can be free'd here. A different copy of the 118 | // buffer is sent to the client, this can be safely cleaned. 119 | } 120 | 121 | int main() { 122 | shm::rpc::Server server("channel_name", callback, clean_up); 123 | 124 | // Using `serve_once` with a manual loop 125 | while(true) { 126 | server.serve_once(); 127 | } 128 | // OR 129 | // Using `serve` 130 | server.serve(); 131 | } 132 | -------------------------------------------------------------------------------- /benchmark/dragons.cpp: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | #include "shadesmar/memory/dragons.h" 24 | 25 | #include 26 | 27 | #include 28 | 29 | #define STARTRANGE 1 << 5 // 32 bytes 30 | #define ENDRANGE 1 << 23 // 8 MB 31 | #define SHMRANGE STARTRANGE, ENDRANGE 32 | 33 | template 34 | void CopyBench(benchmark::State &state) { // NOLINT 35 | CopierT cpy; 36 | size_t size = state.range(0); 37 | auto *src = cpy.alloc(size); 38 | auto *dst = cpy.alloc(size); 39 | std::memset(src, 'x', size); 40 | for (auto _ : state) { 41 | cpy.shm_to_user(dst, src, size); 42 | benchmark::DoNotOptimize(dst); 43 | } 44 | cpy.dealloc(src); 45 | cpy.dealloc(dst); 46 | state.SetBytesProcessed(size * static_cast(state.iterations())); 47 | } 48 | 49 | #define SHM_COPY_BENCHMARK(c) \ 50 | BENCHMARK_TEMPLATE(CopyBench, c)->Range(SHMRANGE); 51 | 52 | SHM_COPY_BENCHMARK(shm::memory::DefaultCopier); 53 | #ifdef __x86_64__ 54 | SHM_COPY_BENCHMARK(shm::memory::dragons::RepMovsbCopier); 55 | #endif 56 | #ifdef __AVX__ 57 | SHM_COPY_BENCHMARK(shm::memory::dragons::AvxCopier); 58 | SHM_COPY_BENCHMARK(shm::memory::dragons::AvxUnrollCopier); 59 | #endif 60 | #ifdef __AVX2__ 61 | SHM_COPY_BENCHMARK(shm::memory::dragons::AvxAsyncCopier); 62 | SHM_COPY_BENCHMARK(shm::memory::dragons::AvxAsyncPFCopier); 63 | SHM_COPY_BENCHMARK(shm::memory::dragons::AvxAsyncUnrollCopier); 64 | SHM_COPY_BENCHMARK(shm::memory::dragons::AvxAsyncPFUnrollCopier); 65 | #endif 66 | 67 | BENCHMARK_MAIN(); 68 | -------------------------------------------------------------------------------- /benchmark/pubsub.cpp: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #ifdef SINGLE_HEADER 29 | #include "shadesmar.h" 30 | #else 31 | #include "shadesmar/memory/copier.h" 32 | #include "shadesmar/memory/dragons.h" 33 | #include "shadesmar/pubsub/publisher.h" 34 | #include "shadesmar/pubsub/subscriber.h" 35 | #include "shadesmar/stats.h" 36 | #endif 37 | 38 | const char topic[] = "raw_benchmark_topic_pubsub"; 39 | 40 | shm::stats::Welford per_second_lag; 41 | 42 | struct Message { 43 | uint64_t count; 44 | uint64_t timestamp; 45 | uint8_t *data; 46 | }; 47 | 48 | uint64_t current_count = 0; 49 | 50 | void callback(shm::memory::Memblock *memblock) { 51 | if (memblock->is_empty()) { 52 | return; 53 | } 54 | auto *msg = reinterpret_cast(memblock->ptr); 55 | auto lag = shm::current_time() - msg->timestamp; 56 | // assert(current_count == msg->count - 1); 57 | assert(current_count < msg->count); 58 | current_count = msg->count; 59 | per_second_lag.add(lag); 60 | } 61 | 62 | void subscribe_loop(int seconds) { 63 | shm::pubsub::Subscriber sub(topic, callback); 64 | 65 | for (int sec = 0; sec < seconds; ++sec) { 66 | auto start = std::chrono::steady_clock::now(); 67 | for (auto now = start; now < start + std::chrono::seconds(1); 68 | now = std::chrono::steady_clock::now()) { 69 | sub.spin_once(); 70 | } 71 | std::cout << per_second_lag << std::endl; 72 | per_second_lag.clear(); 73 | } 74 | } 75 | 76 | void publish_loop(int seconds, int vector_size) { 77 | std::cout << "Number of bytes = " << vector_size << std::endl 78 | << "Time unit = " << TIMESCALE_NAME << std::endl; 79 | 80 | auto *rawptr = malloc(vector_size); 81 | std::memset(rawptr, 255, vector_size); 82 | Message *msg = reinterpret_cast(rawptr); 83 | msg->count = 0; 84 | 85 | shm::pubsub::Publisher pub(topic); 86 | 87 | auto start = std::chrono::steady_clock::now(); 88 | for (auto now = start; now < start + std::chrono::seconds(seconds); 89 | now = std::chrono::steady_clock::now()) { 90 | msg->count++; 91 | msg->timestamp = shm::current_time(); 92 | pub.publish(msg, vector_size); 93 | } 94 | free(msg); 95 | } 96 | 97 | int main() { 98 | const int SECONDS = 10; 99 | const int VECTOR_SIZE = 32 * 1024; 100 | std::thread subscribe_thread(subscribe_loop, SECONDS); 101 | std::thread publish_thread(publish_loop, SECONDS, VECTOR_SIZE); 102 | 103 | subscribe_thread.join(); 104 | publish_thread.join(); 105 | } 106 | -------------------------------------------------------------------------------- /benchmark/rpc.cpp: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2021 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #ifdef SINGLE_HEADER 29 | #include "shadesmar.h" 30 | #else 31 | #include "shadesmar/memory/copier.h" 32 | #include "shadesmar/memory/dragons.h" 33 | #include "shadesmar/rpc/client.h" 34 | #include "shadesmar/rpc/server.h" 35 | #include "shadesmar/stats.h" 36 | #endif 37 | 38 | const char topic[] = "raw_benchmark_topic_rpc"; 39 | 40 | shm::stats::Welford per_second_lag; 41 | 42 | struct Message { 43 | uint64_t count; 44 | uint64_t timestamp; 45 | uint8_t *data; 46 | }; 47 | 48 | uint64_t current_count = 0; 49 | uint8_t *resp_buffer = nullptr; 50 | uint64_t resp_size = 0; 51 | 52 | bool callback(const shm::memory::Memblock &req, shm::memory::Memblock *resp) { 53 | auto *msg = reinterpret_cast(req.ptr); 54 | auto lag = shm::current_time() - msg->timestamp; 55 | std::cout << current_count << ", " << msg->count << "\n"; 56 | assert(current_count < msg->count); 57 | current_count = msg->count; 58 | per_second_lag.add(lag); 59 | resp->ptr = resp_buffer; 60 | resp->size = resp_size; 61 | return true; 62 | } 63 | 64 | void cleanup(shm::memory::Memblock *resp) {} 65 | 66 | void server_loop(int seconds) { 67 | shm::rpc::Server server(topic, callback, cleanup); 68 | 69 | for (int sec = 0; sec < seconds; ++sec) { 70 | auto start = std::chrono::steady_clock::now(); 71 | for (auto now = start; now < start + std::chrono::seconds(1); 72 | now = std::chrono::steady_clock::now()) { 73 | server.serve_once(); 74 | } 75 | std::cout << per_second_lag << std::endl; 76 | per_second_lag.clear(); 77 | } 78 | } 79 | 80 | void client_loop(int seconds, int vector_size) { 81 | std::cout << "Number of bytes = " << vector_size << std::endl; 82 | std::cout << "Time unit = " << TIMESCALE_NAME << std::endl; 83 | 84 | resp_buffer = reinterpret_cast(malloc(vector_size)); 85 | std::memset(resp_buffer, 255, vector_size); 86 | resp_size = vector_size; 87 | 88 | auto *rawptr = malloc(vector_size); 89 | std::memset(rawptr, 255, vector_size); 90 | Message *msg = reinterpret_cast(rawptr); 91 | msg->count = 0; 92 | 93 | shm::rpc::Client client(topic); 94 | 95 | auto start = std::chrono::steady_clock::now(); 96 | for (auto now = start; now < start + std::chrono::seconds(seconds); 97 | now = std::chrono::steady_clock::now()) { 98 | msg->count++; 99 | msg->timestamp = shm::current_time(); 100 | shm::memory::Memblock req, resp; 101 | req.ptr = msg; 102 | req.size = vector_size; 103 | client.call(req, &resp); 104 | } 105 | free(msg); 106 | } 107 | 108 | int main() { 109 | const int SECONDS = 10; 110 | const int VECTOR_SIZE = 32 * 1024; 111 | std::thread server_thread(server_loop, SECONDS); 112 | std::thread client_thread(client_loop, SECONDS, VECTOR_SIZE); 113 | 114 | server_thread.join(); 115 | client_thread.join(); 116 | } 117 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir build 4 | cd build 5 | cmake -DCMAKE_BUILD_TYPE=$1 .. 6 | -------------------------------------------------------------------------------- /include/shadesmar/concurrency/cond_var.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_CONCURRENCY_COND_VAR_H_ 25 | #define INCLUDE_SHADESMAR_CONCURRENCY_COND_VAR_H_ 26 | 27 | #include 28 | 29 | #include "shadesmar/concurrency/lock.h" 30 | 31 | namespace shm::concurrent { 32 | 33 | class CondVar { 34 | public: 35 | CondVar(); 36 | ~CondVar(); 37 | 38 | void wait(PthreadWriteLock *lock); 39 | void signal(); 40 | void reset(); 41 | 42 | private: 43 | pthread_condattr_t attr; 44 | pthread_cond_t cond; 45 | }; 46 | 47 | CondVar::CondVar() { 48 | pthread_condattr_init(&attr); 49 | pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); 50 | pthread_cond_init(&cond, &attr); 51 | } 52 | 53 | CondVar::~CondVar() { 54 | pthread_condattr_destroy(&attr); 55 | pthread_cond_destroy(&cond); 56 | } 57 | 58 | void CondVar::wait(PthreadWriteLock *lock) { 59 | pthread_cond_wait(&cond, lock->get_mutex()); 60 | } 61 | 62 | void CondVar::signal() { pthread_cond_signal(&cond); } 63 | 64 | void CondVar::reset() { 65 | pthread_cond_destroy(&cond); 66 | pthread_cond_init(&cond, &attr); 67 | } 68 | 69 | } // namespace shm::concurrent 70 | 71 | #endif // INCLUDE_SHADESMAR_CONCURRENCY_COND_VAR_H_ 72 | -------------------------------------------------------------------------------- /include/shadesmar/concurrency/lock.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_CONCURRENCY_LOCK_H_ 25 | #define INCLUDE_SHADESMAR_CONCURRENCY_LOCK_H_ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | namespace shm::concurrent { 33 | 34 | class PthreadWriteLock { 35 | public: 36 | PthreadWriteLock(); 37 | ~PthreadWriteLock(); 38 | 39 | void lock(); 40 | bool try_lock(); 41 | void unlock(); 42 | void reset(); 43 | 44 | pthread_mutex_t *get_mutex() { return &mutex; } 45 | 46 | private: 47 | pthread_mutex_t mutex{}; 48 | pthread_mutexattr_t attr{}; 49 | }; 50 | 51 | PthreadWriteLock::PthreadWriteLock() { 52 | pthread_mutexattr_init(&attr); 53 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); 54 | pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); 55 | #ifdef __linux__ 56 | pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); 57 | #endif // __linux__ 58 | pthread_mutex_init(&mutex, &attr); 59 | } 60 | 61 | PthreadWriteLock::~PthreadWriteLock() { 62 | pthread_mutexattr_destroy(&attr); 63 | pthread_mutex_destroy(&mutex); 64 | } 65 | 66 | void PthreadWriteLock::lock() { 67 | pthread_mutex_lock(&mutex); 68 | #ifdef __linux__ 69 | if (errno == EOWNERDEAD) { 70 | std::cerr << "Previous owner of mutex was dead." << std::endl; 71 | } 72 | #endif // __linux__ 73 | } 74 | 75 | bool PthreadWriteLock::try_lock() { return pthread_mutex_trylock(&mutex); } 76 | 77 | void PthreadWriteLock::unlock() { pthread_mutex_unlock(&mutex); } 78 | 79 | void PthreadWriteLock::reset() { 80 | pthread_mutex_destroy(&mutex); 81 | pthread_mutex_init(&mutex, &attr); 82 | } 83 | 84 | } // namespace shm::concurrent 85 | #endif // INCLUDE_SHADESMAR_CONCURRENCY_LOCK_H_ 86 | -------------------------------------------------------------------------------- /include/shadesmar/concurrency/lockless_set.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_CONCURRENCY_LOCKLESS_SET_H_ 25 | #define INCLUDE_SHADESMAR_CONCURRENCY_LOCKLESS_SET_H_ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include "shadesmar/macros.h" 32 | 33 | namespace shm::concurrent { 34 | template 35 | class LocklessSet { 36 | public: 37 | LocklessSet(); 38 | LocklessSet &operator=(const LocklessSet &); 39 | 40 | bool insert(uint32_t elem); 41 | bool remove(uint32_t elem); 42 | 43 | std::array array_ = {}; 44 | }; 45 | 46 | template 47 | LocklessSet::LocklessSet() = default; 48 | 49 | template 50 | LocklessSet &LocklessSet::operator=(const LocklessSet &set) { 51 | for (uint32_t idx = 0; idx < Size; ++idx) { 52 | array_[idx].store(set.array_[idx].load()); 53 | } 54 | return *this; 55 | } 56 | 57 | template 58 | bool LocklessSet::insert(uint32_t elem) { 59 | for (uint32_t idx = 0; idx < Size; ++idx) { 60 | auto probedElem = array_[idx].load(); 61 | 62 | if (probedElem != elem) { 63 | if (probedElem != 0) { 64 | continue; 65 | } 66 | uint32_t exp = 0; 67 | if (array_[idx].compare_exchange_strong(exp, elem)) { 68 | return true; 69 | } else { 70 | continue; 71 | } 72 | } 73 | return false; 74 | } 75 | return false; 76 | } 77 | 78 | template 79 | bool LocklessSet::remove(uint32_t elem) { 80 | for (uint32_t idx = 0; idx < Size; ++idx) { 81 | auto probedElem = array_[idx].load(); 82 | 83 | if (probedElem == elem) { 84 | return array_[idx].compare_exchange_strong(elem, 0); 85 | } 86 | } 87 | 88 | return false; 89 | } 90 | } // namespace shm::concurrent 91 | 92 | #endif // INCLUDE_SHADESMAR_CONCURRENCY_LOCKLESS_SET_H_ 93 | -------------------------------------------------------------------------------- /include/shadesmar/concurrency/robust_lock.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_CONCURRENCY_ROBUST_LOCK_H_ 25 | #define INCLUDE_SHADESMAR_CONCURRENCY_ROBUST_LOCK_H_ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "shadesmar/concurrency/lock.h" 35 | #include "shadesmar/concurrency/lockless_set.h" 36 | #include "shadesmar/concurrency/rw_lock.h" 37 | #include "shadesmar/macros.h" 38 | 39 | namespace shm::concurrent { 40 | 41 | class RobustLock { 42 | public: 43 | RobustLock(); 44 | RobustLock(const RobustLock &); 45 | ~RobustLock(); 46 | 47 | void lock(); 48 | bool try_lock(); 49 | void unlock(); 50 | void lock_sharable(); 51 | bool try_lock_sharable(); 52 | void unlock_sharable(); 53 | void reset(); 54 | 55 | private: 56 | void prune_readers(); 57 | PthreadReadWriteLock mutex_; 58 | std::atomic<__pid_t> exclusive_owner{0}; 59 | LocklessSet<8> shared_owners; 60 | }; 61 | 62 | RobustLock::RobustLock() = default; 63 | 64 | RobustLock::RobustLock(const RobustLock &lock) { 65 | mutex_ = lock.mutex_; 66 | exclusive_owner.store(lock.exclusive_owner.load()); 67 | shared_owners = lock.shared_owners; 68 | } 69 | 70 | RobustLock::~RobustLock() { exclusive_owner = 0; } 71 | 72 | void RobustLock::lock() { 73 | while (!mutex_.try_lock()) { 74 | if (exclusive_owner.load() != 0) { 75 | auto ex_proc = exclusive_owner.load(); 76 | if (proc_dead(ex_proc)) { 77 | if (exclusive_owner.compare_exchange_strong(ex_proc, 0)) { 78 | mutex_.unlock(); 79 | continue; 80 | } 81 | } 82 | } else { 83 | prune_readers(); 84 | } 85 | 86 | std::this_thread::sleep_for(std::chrono::microseconds(1)); 87 | } 88 | exclusive_owner = getpid(); 89 | } 90 | 91 | bool RobustLock::try_lock() { 92 | if (!mutex_.try_lock()) { 93 | if (exclusive_owner != 0) { 94 | auto ex_proc = exclusive_owner.load(); 95 | if (proc_dead(ex_proc)) { 96 | if (exclusive_owner.compare_exchange_strong(ex_proc, 0)) { 97 | mutex_.unlock(); 98 | } 99 | } 100 | } else { 101 | prune_readers(); 102 | } 103 | if (mutex_.try_lock()) { 104 | exclusive_owner = getpid(); 105 | return true; 106 | } else { 107 | return false; 108 | } 109 | } else { 110 | exclusive_owner = getpid(); 111 | return true; 112 | } 113 | } 114 | 115 | void RobustLock::unlock() { 116 | __pid_t current_pid = getpid(); 117 | if (exclusive_owner.compare_exchange_strong(current_pid, 0)) { 118 | mutex_.unlock(); 119 | } 120 | } 121 | 122 | void RobustLock::lock_sharable() { 123 | while (!mutex_.try_lock_sharable()) { 124 | if (exclusive_owner != 0) { 125 | auto ex_proc = exclusive_owner.load(); 126 | if (proc_dead(ex_proc)) { 127 | if (exclusive_owner.compare_exchange_strong(ex_proc, 0)) { 128 | exclusive_owner = 0; 129 | mutex_.unlock(); 130 | } 131 | } 132 | } 133 | 134 | std::this_thread::sleep_for(std::chrono::microseconds(1)); 135 | } 136 | while (!shared_owners.insert(getpid())) { 137 | } 138 | } 139 | 140 | bool RobustLock::try_lock_sharable() { 141 | if (!mutex_.try_lock_sharable()) { 142 | if (exclusive_owner != 0) { 143 | auto ex_proc = exclusive_owner.load(); 144 | if (proc_dead(ex_proc)) { 145 | if (exclusive_owner.compare_exchange_strong(ex_proc, 0)) { 146 | exclusive_owner = 0; 147 | mutex_.unlock(); 148 | } 149 | } 150 | } 151 | if (mutex_.try_lock_sharable()) { 152 | while (!shared_owners.insert(getpid())) { 153 | } 154 | return true; 155 | } else { 156 | return false; 157 | } 158 | } else { 159 | while (!shared_owners.insert(getpid())) { 160 | } 161 | return true; 162 | } 163 | } 164 | 165 | void RobustLock::unlock_sharable() { 166 | if (shared_owners.remove(getpid())) { 167 | mutex_.unlock_sharable(); 168 | } 169 | } 170 | 171 | void RobustLock::reset() { mutex_.reset(); } 172 | 173 | void RobustLock::prune_readers() { 174 | for (auto &i : shared_owners.array_) { 175 | uint32_t shared_owner = i.load(); 176 | 177 | if (shared_owner == 0) continue; 178 | if (proc_dead(shared_owner)) { 179 | if (shared_owners.remove(shared_owner)) { 180 | mutex_.unlock_sharable(); 181 | } 182 | } 183 | } 184 | } 185 | 186 | } // namespace shm::concurrent 187 | #endif // INCLUDE_SHADESMAR_CONCURRENCY_ROBUST_LOCK_H_ 188 | -------------------------------------------------------------------------------- /include/shadesmar/concurrency/rw_lock.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_CONCURRENCY_RW_LOCK_H_ 25 | #define INCLUDE_SHADESMAR_CONCURRENCY_RW_LOCK_H_ 26 | 27 | #include 28 | 29 | #include "shadesmar/concurrency/lock.h" 30 | #include "shadesmar/macros.h" 31 | 32 | namespace shm::concurrent { 33 | 34 | class PthreadReadWriteLock { 35 | public: 36 | PthreadReadWriteLock(); 37 | ~PthreadReadWriteLock(); 38 | 39 | void lock(); 40 | bool try_lock(); 41 | void unlock(); 42 | void lock_sharable(); 43 | bool try_lock_sharable(); 44 | void unlock_sharable(); 45 | void reset(); 46 | 47 | private: 48 | pthread_rwlock_t rwlock{}; 49 | pthread_rwlockattr_t attr{}; 50 | }; 51 | 52 | PthreadReadWriteLock::PthreadReadWriteLock() { 53 | pthread_rwlockattr_init(&attr); 54 | pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); 55 | pthread_rwlock_init(&rwlock, &attr); 56 | } 57 | PthreadReadWriteLock::~PthreadReadWriteLock() { 58 | pthread_rwlockattr_destroy(&attr); 59 | pthread_rwlock_destroy(&rwlock); 60 | } 61 | 62 | void PthreadReadWriteLock::lock() { pthread_rwlock_wrlock(&rwlock); } 63 | bool PthreadReadWriteLock::try_lock() { 64 | return (!pthread_rwlock_trywrlock(&rwlock)); 65 | } 66 | void PthreadReadWriteLock::unlock() { pthread_rwlock_unlock(&rwlock); } 67 | void PthreadReadWriteLock::lock_sharable() { pthread_rwlock_rdlock(&rwlock); } 68 | bool PthreadReadWriteLock::try_lock_sharable() { 69 | return (!pthread_rwlock_tryrdlock(&rwlock)); 70 | } 71 | void PthreadReadWriteLock::unlock_sharable() { 72 | pthread_rwlock_unlock(&rwlock); 73 | } 74 | 75 | void PthreadReadWriteLock::reset() { 76 | pthread_rwlock_destroy(&rwlock); 77 | pthread_rwlock_init(&rwlock, &attr); 78 | } 79 | 80 | } // namespace shm::concurrent 81 | #endif // INCLUDE_SHADESMAR_CONCURRENCY_RW_LOCK_H_ 82 | -------------------------------------------------------------------------------- /include/shadesmar/concurrency/scope.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_CONCURRENCY_SCOPE_H_ 25 | #define INCLUDE_SHADESMAR_CONCURRENCY_SCOPE_H_ 26 | 27 | namespace shm::concurrent { 28 | 29 | enum ExlOrShr { EXCLUSIVE, SHARED }; 30 | 31 | template 32 | class ScopeGuard; 33 | 34 | template 35 | class ScopeGuard { 36 | public: 37 | explicit ScopeGuard(LockT *lck) : lck_(lck) { 38 | if (lck_ != nullptr) { 39 | lck_->lock(); 40 | } 41 | } 42 | 43 | ~ScopeGuard() { 44 | if (lck_ != nullptr) { 45 | lck_->unlock(); 46 | lck_ = nullptr; 47 | } 48 | } 49 | 50 | private: 51 | LockT *lck_; 52 | }; 53 | 54 | template 55 | class ScopeGuard { 56 | public: 57 | explicit ScopeGuard(LockT *lck) : lck_(lck) { lck_->lock_sharable(); } 58 | 59 | ~ScopeGuard() { 60 | if (lck_ != nullptr) { 61 | lck_->unlock_sharable(); 62 | lck_ = nullptr; 63 | } 64 | } 65 | 66 | private: 67 | LockT *lck_; 68 | }; 69 | } // namespace shm::concurrent 70 | #endif // INCLUDE_SHADESMAR_CONCURRENCY_SCOPE_H_ 71 | -------------------------------------------------------------------------------- /include/shadesmar/macros.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_MACROS_H_ 25 | #define INCLUDE_SHADESMAR_MACROS_H_ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #define TIMESCALE std::chrono::microseconds 34 | #define TIMESCALE_COUNT 1e6 35 | #define TIMESCALE_NAME "us" 36 | 37 | namespace shm { 38 | uint64_t current_time() { 39 | auto time_since_epoch = std::chrono::system_clock::now().time_since_epoch(); 40 | auto casted_time = std::chrono::duration_cast(time_since_epoch); 41 | return casted_time.count(); 42 | } 43 | } // namespace shm 44 | 45 | inline bool proc_dead(__pid_t proc) { 46 | if (proc == 0) { 47 | return false; 48 | } 49 | std::string pid_path = "/proc/" + std::to_string(proc); 50 | struct stat sts {}; 51 | return (stat(pid_path.c_str(), &sts) == -1 && errno == ENOENT); 52 | } 53 | 54 | #endif // INCLUDE_SHADESMAR_MACROS_H_ 55 | -------------------------------------------------------------------------------- /include/shadesmar/memory/allocator.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_MEMORY_ALLOCATOR_H_ 25 | #define INCLUDE_SHADESMAR_MEMORY_ALLOCATOR_H_ 26 | 27 | #include 28 | 29 | #include "shadesmar/concurrency/robust_lock.h" 30 | #include "shadesmar/concurrency/scope.h" 31 | 32 | namespace shm::memory { 33 | 34 | #define SHMALIGN(s, a) (((s - 1) | (a - 1)) + 1) 35 | 36 | inline uint8_t *align_address(void *ptr, size_t alignment) { 37 | auto int_ptr = reinterpret_cast(ptr); 38 | auto aligned_int_ptr = SHMALIGN(int_ptr, alignment); 39 | return reinterpret_cast(aligned_int_ptr); 40 | } 41 | 42 | class Allocator { 43 | public: 44 | using handle = uint64_t; 45 | 46 | template 47 | using Scope = concurrent::ScopeGuard; 48 | 49 | Allocator(size_t offset, size_t size); 50 | 51 | uint8_t *alloc(uint32_t bytes); 52 | uint8_t *alloc(uint32_t bytes, size_t alignment); 53 | bool free(const uint8_t *ptr); 54 | void reset(); 55 | void lock_reset(); 56 | 57 | inline handle ptr_to_handle(uint8_t *p) { 58 | return p - reinterpret_cast(heap_()); 59 | } 60 | uint8_t *handle_to_ptr(handle h) { 61 | return reinterpret_cast(heap_()) + h; 62 | } 63 | 64 | size_t get_free_memory() { 65 | Scope _(&lock_); 66 | 67 | size_t free_size; 68 | size_t size = size_ / sizeof(int); 69 | if (free_index_ <= alloc_index_) { 70 | free_size = size - alloc_index_ + free_index_; 71 | } else { 72 | free_size = free_index_ - alloc_index_; 73 | } 74 | 75 | return free_size * sizeof(int); 76 | } 77 | 78 | concurrent::RobustLock lock_; 79 | 80 | private: 81 | void validate_index(uint32_t index) const; 82 | [[nodiscard]] uint32_t suggest_index(uint32_t header_index, 83 | uint32_t payload_size) const; 84 | uint32_t *__attribute__((always_inline)) heap_() { 85 | return reinterpret_cast(reinterpret_cast(this) + 86 | offset_); 87 | } 88 | 89 | uint32_t alloc_index_; 90 | volatile uint32_t free_index_; 91 | size_t offset_; 92 | size_t size_; 93 | }; 94 | 95 | Allocator::Allocator(size_t offset, size_t size) 96 | : alloc_index_(0), free_index_(0), offset_(offset), size_(size) { 97 | assert(!(size & (sizeof(int) - 1))); 98 | } 99 | 100 | void Allocator::validate_index(uint32_t index) const { 101 | assert(index < (size_ / sizeof(int))); 102 | } 103 | 104 | uint32_t Allocator::suggest_index(uint32_t header_index, 105 | uint32_t payload_size) const { 106 | validate_index(header_index); 107 | 108 | int32_t payload_index = header_index + 1; 109 | 110 | if (payload_index + payload_size - 1 >= size_ / sizeof(int)) { 111 | payload_index = 0; 112 | } 113 | validate_index(payload_index); 114 | validate_index(payload_index + payload_size - 1); 115 | return payload_index; 116 | } 117 | 118 | uint8_t *Allocator::alloc(uint32_t bytes) { return alloc(bytes, 1); } 119 | 120 | uint8_t *Allocator::alloc(uint32_t bytes, size_t alignment) { 121 | uint32_t payload_size = bytes + alignment; 122 | 123 | if (payload_size == 0) { 124 | payload_size = sizeof(int); 125 | } 126 | 127 | if (payload_size >= size_ - 2 * sizeof(int)) { 128 | return nullptr; 129 | } 130 | 131 | if (payload_size & (sizeof(int) - 1)) { 132 | payload_size &= ~(sizeof(int) - 1); 133 | payload_size += sizeof(int); 134 | } 135 | 136 | payload_size /= sizeof(int); 137 | 138 | Scope _(&lock_); 139 | 140 | const auto payload_index = suggest_index(alloc_index_, payload_size); 141 | const auto free_index_th = free_index_; 142 | uint32_t new_alloc_index = payload_index + payload_size; 143 | 144 | if (alloc_index_ < free_index_th && payload_index == 0) { 145 | return nullptr; 146 | } 147 | 148 | if (payload_index <= free_index_th && free_index_th <= new_alloc_index) { 149 | return nullptr; 150 | } 151 | 152 | if (new_alloc_index == size_ / sizeof(int)) { 153 | new_alloc_index = 0; 154 | if (new_alloc_index == free_index_th) { 155 | return nullptr; 156 | } 157 | } 158 | 159 | assert(new_alloc_index != alloc_index_); 160 | validate_index(new_alloc_index); 161 | 162 | heap_()[alloc_index_] = payload_size; 163 | alloc_index_ = new_alloc_index; 164 | 165 | auto heap_ptr = reinterpret_cast(heap_() + payload_index); 166 | return align_address(heap_ptr, alignment); 167 | } 168 | 169 | bool Allocator::free(const uint8_t *ptr) { 170 | if (ptr == nullptr) { 171 | return true; 172 | } 173 | auto *heap = reinterpret_cast(heap_()); 174 | 175 | Scope _(&lock_); 176 | 177 | assert(ptr >= heap); 178 | assert(ptr < heap + size_); 179 | validate_index(free_index_); 180 | 181 | uint32_t payload_size = heap_()[free_index_]; 182 | uint32_t payload_index = suggest_index(free_index_, payload_size); 183 | 184 | if (ptr != reinterpret_cast(heap_() + payload_index)) { 185 | return false; 186 | } 187 | 188 | uint32_t new_free_index = payload_index + payload_size; 189 | if (new_free_index == size_ / sizeof(int)) { 190 | new_free_index = 0; 191 | } 192 | free_index_ = new_free_index; 193 | return true; 194 | } 195 | 196 | void Allocator::reset() { 197 | alloc_index_ = 0; 198 | free_index_ = 0; 199 | } 200 | 201 | void Allocator::lock_reset() { lock_.reset(); } 202 | 203 | } // namespace shm::memory 204 | #endif // INCLUDE_SHADESMAR_MEMORY_ALLOCATOR_H_ 205 | -------------------------------------------------------------------------------- /include/shadesmar/memory/copier.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_MEMORY_COPIER_H_ 25 | #define INCLUDE_SHADESMAR_MEMORY_COPIER_H_ 26 | 27 | #include 28 | #include 29 | 30 | namespace shm::memory { 31 | class Copier { 32 | public: 33 | virtual ~Copier() = default; 34 | // Each derived class of `Copier` needs to also add `PtrT` for compatibility 35 | // with`MTCopier`. See `DefaultCopier` for example. 36 | virtual void *alloc(size_t) = 0; 37 | virtual void dealloc(void *) = 0; 38 | virtual void shm_to_user(void *, void *, size_t) = 0; 39 | virtual void user_to_shm(void *, void *, size_t) = 0; 40 | }; 41 | 42 | class DefaultCopier : public Copier { 43 | public: 44 | using PtrT = uint8_t; 45 | void *alloc(size_t size) override { return malloc(size); } 46 | 47 | void dealloc(void *ptr) override { free(ptr); } 48 | 49 | void shm_to_user(void *dst, void *src, size_t size) override { 50 | std::memcpy(dst, src, size); 51 | } 52 | 53 | void user_to_shm(void *dst, void *src, size_t size) override { 54 | std::memcpy(dst, src, size); 55 | } 56 | }; 57 | 58 | } // namespace shm::memory 59 | 60 | #endif // INCLUDE_SHADESMAR_MEMORY_COPIER_H_ 61 | -------------------------------------------------------------------------------- /include/shadesmar/memory/double_allocator.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2021 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_MEMORY_DOUBLE_ALLOCATOR_H_ 25 | #define INCLUDE_SHADESMAR_MEMORY_DOUBLE_ALLOCATOR_H_ 26 | 27 | #include "shadesmar/memory/allocator.h" 28 | 29 | namespace shm::memory { 30 | 31 | class DoubleAllocator { 32 | public: 33 | DoubleAllocator(size_t offset, size_t size) 34 | : req(offset, size / 2), resp(offset + size / 2, size / 2) {} 35 | Allocator req; 36 | Allocator resp; 37 | 38 | void reset() { 39 | req.reset(); 40 | resp.reset(); 41 | } 42 | 43 | void lock_reset() { 44 | req.lock_reset(); 45 | resp.lock_reset(); 46 | } 47 | }; 48 | 49 | } // namespace shm::memory 50 | 51 | #endif // INCLUDE_SHADESMAR_MEMORY_DOUBLE_ALLOCATOR_H_ 52 | -------------------------------------------------------------------------------- /include/shadesmar/memory/dragons.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | // HERE BE DRAGONS 25 | 26 | #ifndef INCLUDE_SHADESMAR_MEMORY_DRAGONS_H_ 27 | #define INCLUDE_SHADESMAR_MEMORY_DRAGONS_H_ 28 | 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #include "shadesmar/memory/copier.h" 36 | #include "shadesmar/memory/memory.h" 37 | 38 | namespace shm::memory::dragons { 39 | 40 | //------------------------------------------------------------------------------ 41 | 42 | #ifdef __x86_64__ 43 | 44 | static inline void _rep_movsb(void *d, const void *s, size_t n) { 45 | asm volatile("rep movsb" 46 | : "=D"(d), "=S"(s), "=c"(n) 47 | : "0"(d), "1"(s), "2"(n) 48 | : "memory"); 49 | } 50 | 51 | class RepMovsbCopier : public Copier { 52 | public: 53 | using PtrT = uint8_t; 54 | void *alloc(size_t size) override { return malloc(size); } 55 | 56 | void dealloc(void *ptr) override { free(ptr); } 57 | 58 | void shm_to_user(void *dst, void *src, size_t size) override { 59 | _rep_movsb(dst, src, size); 60 | } 61 | 62 | void user_to_shm(void *dst, void *src, size_t size) override { 63 | _rep_movsb(dst, src, size); 64 | } 65 | }; 66 | 67 | #endif // __x86_64__ 68 | 69 | //------------------------------------------------------------------------------ 70 | 71 | #ifdef __AVX__ 72 | 73 | static inline void _avx_cpy(void *d, const void *s, size_t n) { 74 | // d, s -> 32 byte aligned 75 | // n -> multiple of 32 76 | 77 | auto *dVec = reinterpret_cast<__m256i *>(d); 78 | const auto *sVec = reinterpret_cast(s); 79 | size_t nVec = n / sizeof(__m256i); 80 | for (; nVec > 0; nVec--, sVec++, dVec++) { 81 | const __m256i temp = _mm256_load_si256(sVec); 82 | _mm256_store_si256(dVec, temp); 83 | } 84 | } 85 | 86 | class AvxCopier : public Copier { 87 | public: 88 | using PtrT = __m256i; 89 | constexpr static size_t alignment = sizeof(__m256i); 90 | 91 | void *alloc(size_t size) override { 92 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 93 | } 94 | 95 | void dealloc(void *ptr) override { free(ptr); } 96 | 97 | void shm_to_user(void *dst, void *src, size_t size) override { 98 | _avx_cpy(dst, src, SHMALIGN(size, alignment)); 99 | } 100 | 101 | void user_to_shm(void *dst, void *src, size_t size) override { 102 | _avx_cpy(dst, src, SHMALIGN(size, alignment)); 103 | } 104 | }; 105 | 106 | #endif // __AVX__ 107 | 108 | //------------------------------------------------------------------------------ 109 | 110 | #ifdef __AVX2__ 111 | 112 | static inline void _avx_async_cpy(void *d, const void *s, size_t n) { 113 | // d, s -> 32 byte aligned 114 | // n -> multiple of 32 115 | 116 | auto *dVec = reinterpret_cast<__m256i *>(d); 117 | const auto *sVec = reinterpret_cast(s); 118 | size_t nVec = n / sizeof(__m256i); 119 | for (; nVec > 0; nVec--, sVec++, dVec++) { 120 | const __m256i temp = _mm256_stream_load_si256(sVec); 121 | _mm256_stream_si256(dVec, temp); 122 | } 123 | _mm_sfence(); 124 | } 125 | 126 | class AvxAsyncCopier : public Copier { 127 | public: 128 | using PtrT = __m256i; 129 | constexpr static size_t alignment = sizeof(__m256i); 130 | 131 | void *alloc(size_t size) override { 132 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 133 | } 134 | 135 | void dealloc(void *ptr) override { free(ptr); } 136 | 137 | void shm_to_user(void *dst, void *src, size_t size) override { 138 | _avx_async_cpy(dst, src, SHMALIGN(size, alignment)); 139 | } 140 | 141 | void user_to_shm(void *dst, void *src, size_t size) override { 142 | _avx_async_cpy(dst, src, SHMALIGN(size, alignment)); 143 | } 144 | }; 145 | 146 | #endif // __AVX2__ 147 | 148 | //------------------------------------------------------------------------------ 149 | 150 | #ifdef __AVX2__ 151 | 152 | static inline void _avx_async_pf_cpy(void *d, const void *s, size_t n) { 153 | // d, s -> 64 byte aligned 154 | // n -> multiple of 64 155 | 156 | auto *dVec = reinterpret_cast<__m256i *>(d); 157 | const auto *sVec = reinterpret_cast(s); 158 | size_t nVec = n / sizeof(__m256i); 159 | for (; nVec > 2; nVec -= 2, sVec += 2, dVec += 2) { 160 | // prefetch the next iteration's data 161 | // by default _mm_prefetch moves the entire cache-lint (64b) 162 | _mm_prefetch(sVec + 2, _MM_HINT_T0); 163 | 164 | _mm256_stream_si256(dVec, _mm256_load_si256(sVec)); 165 | _mm256_stream_si256(dVec + 1, _mm256_load_si256(sVec + 1)); 166 | } 167 | _mm256_stream_si256(dVec, _mm256_load_si256(sVec)); 168 | _mm256_stream_si256(dVec + 1, _mm256_load_si256(sVec + 1)); 169 | _mm_sfence(); 170 | } 171 | 172 | class AvxAsyncPFCopier : public Copier { 173 | public: 174 | using PtrT = __m256i; 175 | constexpr static size_t alignment = sizeof(__m256i) * 2; 176 | 177 | void *alloc(size_t size) override { 178 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 179 | } 180 | 181 | void dealloc(void *ptr) override { free(ptr); } 182 | 183 | void shm_to_user(void *dst, void *src, size_t size) override { 184 | _avx_async_pf_cpy(dst, src, SHMALIGN(size, alignment)); 185 | } 186 | 187 | void user_to_shm(void *dst, void *src, size_t size) override { 188 | _avx_async_pf_cpy(dst, src, SHMALIGN(size, alignment)); 189 | } 190 | }; 191 | 192 | #endif // __AVX2__ 193 | 194 | //------------------------------------------------------------------------------ 195 | 196 | #ifdef __AVX__ 197 | 198 | static inline void _avx_cpy_unroll(void *d, const void *s, size_t n) { 199 | // d, s -> 128 byte aligned 200 | // n -> multiple of 128 201 | 202 | auto *dVec = reinterpret_cast<__m256i *>(d); 203 | const auto *sVec = reinterpret_cast(s); 204 | size_t nVec = n / sizeof(__m256i); 205 | for (; nVec > 0; nVec -= 4, sVec += 4, dVec += 4) { 206 | _mm256_store_si256(dVec, _mm256_load_si256(sVec)); 207 | _mm256_store_si256(dVec + 1, _mm256_load_si256(sVec + 1)); 208 | _mm256_store_si256(dVec + 2, _mm256_load_si256(sVec + 2)); 209 | _mm256_store_si256(dVec + 3, _mm256_load_si256(sVec + 3)); 210 | } 211 | } 212 | 213 | class AvxUnrollCopier : public Copier { 214 | public: 215 | using PtrT = __m256i; 216 | constexpr static size_t alignment = 4 * sizeof(__m256i); 217 | 218 | void *alloc(size_t size) override { 219 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 220 | } 221 | 222 | void dealloc(void *ptr) override { free(ptr); } 223 | 224 | void shm_to_user(void *dst, void *src, size_t size) override { 225 | _avx_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 226 | } 227 | 228 | void user_to_shm(void *dst, void *src, size_t size) override { 229 | _avx_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 230 | } 231 | }; 232 | 233 | #endif // __AVX__ 234 | 235 | //------------------------------------------------------------------------------ 236 | 237 | #ifdef __AVX2__ 238 | 239 | static inline void _avx_async_cpy_unroll(void *d, const void *s, size_t n) { 240 | // d, s -> 128 byte aligned 241 | // n -> multiple of 128 242 | 243 | auto *dVec = reinterpret_cast<__m256i *>(d); 244 | const auto *sVec = reinterpret_cast(s); 245 | size_t nVec = n / sizeof(__m256i); 246 | for (; nVec > 0; nVec -= 4, sVec += 4, dVec += 4) { 247 | _mm256_stream_si256(dVec, _mm256_stream_load_si256(sVec)); 248 | _mm256_stream_si256(dVec + 1, _mm256_stream_load_si256(sVec + 1)); 249 | _mm256_stream_si256(dVec + 2, _mm256_stream_load_si256(sVec + 2)); 250 | _mm256_stream_si256(dVec + 3, _mm256_stream_load_si256(sVec + 3)); 251 | } 252 | _mm_sfence(); 253 | } 254 | 255 | class AvxAsyncUnrollCopier : public Copier { 256 | public: 257 | using PtrT = __m256i; 258 | constexpr static size_t alignment = 4 * sizeof(__m256i); 259 | 260 | void *alloc(size_t size) override { 261 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 262 | } 263 | 264 | void dealloc(void *ptr) override { free(ptr); } 265 | 266 | void shm_to_user(void *dst, void *src, size_t size) override { 267 | _avx_async_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 268 | } 269 | 270 | void user_to_shm(void *dst, void *src, size_t size) override { 271 | _avx_async_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 272 | } 273 | }; 274 | 275 | #endif // __AVX2__ 276 | 277 | //------------------------------------------------------------------------------ 278 | 279 | #ifdef __AVX2__ 280 | 281 | static inline void _avx_async_pf_cpy_unroll(void *d, const void *s, size_t n) { 282 | // d, s -> 128 byte aligned 283 | // n -> multiple of 128 284 | 285 | auto *dVec = reinterpret_cast<__m256i *>(d); 286 | const auto *sVec = reinterpret_cast(s); 287 | size_t nVec = n / sizeof(__m256i); 288 | for (; nVec > 4; nVec -= 4, sVec += 4, dVec += 4) { 289 | // prefetch data for next iteration 290 | _mm_prefetch(sVec + 4, _MM_HINT_T0); 291 | _mm_prefetch(sVec + 6, _MM_HINT_T0); 292 | _mm256_stream_si256(dVec, _mm256_load_si256(sVec)); 293 | _mm256_stream_si256(dVec + 1, _mm256_load_si256(sVec + 1)); 294 | _mm256_stream_si256(dVec + 2, _mm256_load_si256(sVec + 2)); 295 | _mm256_stream_si256(dVec + 3, _mm256_load_si256(sVec + 3)); 296 | } 297 | _mm256_stream_si256(dVec, _mm256_load_si256(sVec)); 298 | _mm256_stream_si256(dVec + 1, _mm256_load_si256(sVec + 1)); 299 | _mm256_stream_si256(dVec + 2, _mm256_load_si256(sVec + 2)); 300 | _mm256_stream_si256(dVec + 3, _mm256_load_si256(sVec + 3)); 301 | _mm_sfence(); 302 | } 303 | 304 | class AvxAsyncPFUnrollCopier : public Copier { 305 | public: 306 | using PtrT = __m256i; 307 | constexpr static size_t alignment = 4 * sizeof(__m256i); 308 | 309 | void *alloc(size_t size) override { 310 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 311 | } 312 | 313 | void dealloc(void *ptr) override { free(ptr); } 314 | 315 | void shm_to_user(void *dst, void *src, size_t size) override { 316 | _avx_async_pf_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 317 | } 318 | 319 | void user_to_shm(void *dst, void *src, size_t size) override { 320 | _avx_async_pf_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 321 | } 322 | }; 323 | 324 | #endif // __AVX2__ 325 | 326 | //------------------------------------------------------------------------------ 327 | 328 | template 329 | class MTCopier : public Copier { 330 | public: 331 | MTCopier() : base_copier() {} 332 | 333 | void *alloc(size_t size) override { return base_copier.alloc(size); } 334 | 335 | void dealloc(void *ptr) override { base_copier.dealloc(ptr); } 336 | 337 | void _copy(void *d, void *s, size_t n, bool shm_to_user) { 338 | n = SHMALIGN(n, sizeof(typename BaseCopierT::PtrT)) / 339 | sizeof(typename BaseCopierT::PtrT); 340 | std::vector threads; 341 | threads.reserve(nthreads); 342 | 343 | auto per_worker = div((int64_t)n, nthreads); 344 | 345 | size_t next_start = 0; 346 | for (uint32_t thread_idx = 0; thread_idx < nthreads; ++thread_idx) { 347 | const size_t curr_start = next_start; 348 | next_start += per_worker.quot; 349 | if (thread_idx < per_worker.rem) { 350 | ++next_start; 351 | } 352 | auto d_thread = 353 | reinterpret_cast(d) + curr_start; 354 | auto s_thread = 355 | reinterpret_cast(s) + curr_start; 356 | 357 | if (shm_to_user) { 358 | threads.emplace_back( 359 | &Copier::shm_to_user, &base_copier, d_thread, s_thread, 360 | (next_start - curr_start) * sizeof(typename BaseCopierT::PtrT)); 361 | } else { 362 | threads.emplace_back( 363 | &Copier::user_to_shm, &base_copier, d_thread, s_thread, 364 | (next_start - curr_start) * sizeof(typename BaseCopierT::PtrT)); 365 | } 366 | } 367 | for (auto &thread : threads) { 368 | thread.join(); 369 | } 370 | threads.clear(); 371 | } 372 | 373 | void shm_to_user(void *dst, void *src, size_t size) override { 374 | _copy(dst, src, size, true); 375 | } 376 | 377 | void user_to_shm(void *dst, void *src, size_t size) override { 378 | _copy(dst, src, size, false); 379 | } 380 | 381 | private: 382 | BaseCopierT base_copier; 383 | }; 384 | 385 | //------------------------------------------------------------------------------ 386 | 387 | } // namespace shm::memory::dragons 388 | 389 | #endif // INCLUDE_SHADESMAR_MEMORY_DRAGONS_H_ 390 | -------------------------------------------------------------------------------- /include/shadesmar/memory/memory.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_MEMORY_MEMORY_H_ 25 | #define INCLUDE_SHADESMAR_MEMORY_MEMORY_H_ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "shadesmar/concurrency/lockless_set.h" 41 | #include "shadesmar/concurrency/robust_lock.h" 42 | #include "shadesmar/macros.h" 43 | #include "shadesmar/memory/allocator.h" 44 | 45 | namespace shm::memory { 46 | 47 | static constexpr size_t QUEUE_SIZE = 1024; 48 | static size_t buffer_size = (1U << 28); // 256mb 49 | static size_t GAP = 1024; // 1kb safety gap 50 | 51 | inline uint8_t *create_memory_segment(const std::string &name, size_t size, 52 | bool *new_segment, 53 | size_t alignment = 32) { 54 | /* 55 | * Create a new shared memory segment. The segment is created 56 | * under a name. We check if an existing segment is found under 57 | * the same name. This info is stored in `new_segment`. 58 | * 59 | * The permission of the memory segment is 0644. 60 | */ 61 | int fd; 62 | while (true) { 63 | *new_segment = true; 64 | fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644); 65 | if (fd >= 0) { 66 | fchmod(fd, 0644); 67 | } else if (errno == EEXIST) { 68 | fd = shm_open(name.c_str(), O_RDWR, 0644); 69 | if (fd < 0 && errno == ENOENT) { 70 | // the memory segment was deleted in the mean time 71 | continue; 72 | } 73 | *new_segment = false; 74 | } else { 75 | return nullptr; 76 | } 77 | break; 78 | } 79 | 80 | if (*new_segment) { 81 | // Allocate an extra `alignment` bytes as padding 82 | int result = ftruncate(fd, size + alignment); 83 | if (result == EINVAL) { 84 | return nullptr; 85 | } 86 | } 87 | 88 | auto *ptr = mmap(nullptr, size + alignment, PROT_READ | PROT_WRITE, 89 | MAP_SHARED, fd, 0); 90 | return align_address(ptr, alignment); 91 | } 92 | 93 | struct Memblock { 94 | void *ptr; 95 | size_t size; 96 | bool free; 97 | 98 | Memblock() : ptr(nullptr), size(0), free(true) {} 99 | Memblock(void *ptr, size_t size) : ptr(ptr), size(size), free(true) {} 100 | 101 | void no_delete() { free = false; } 102 | 103 | bool is_empty() const { return ptr == nullptr && size == 0; } 104 | }; 105 | 106 | class PIDSet { 107 | public: 108 | bool any_alive() { 109 | // TODO(squadrick): Make sure it is atomic 110 | bool alive = false; 111 | uint32_t current_pid = getpid(); 112 | for (auto &i : pid_set.array_) { 113 | uint32_t pid = i.load(); 114 | 115 | if (pid == 0) continue; 116 | 117 | if (pid == current_pid) { 118 | // intra-process communication 119 | // two threads of the same process 120 | alive = true; 121 | } 122 | 123 | if (proc_dead(pid)) { 124 | while (!pid_set.remove(pid)) { 125 | } 126 | } else { 127 | alive = true; 128 | } 129 | } 130 | return alive; 131 | } 132 | 133 | bool insert(uint32_t pid) { return pid_set.insert(pid); } 134 | 135 | void lock() { lck.lock(); } 136 | 137 | void unlock() { lck.unlock(); } 138 | 139 | private: 140 | concurrent::LocklessSet<32> pid_set; 141 | concurrent::RobustLock lck; 142 | }; 143 | 144 | struct Element { 145 | size_t size; 146 | bool empty; 147 | Allocator::handle address_handle; 148 | 149 | Element() : size(0), address_handle(0), empty(true) {} 150 | 151 | void reset() { 152 | size = 0; 153 | address_handle = 0; 154 | empty = true; 155 | } 156 | }; 157 | 158 | template 159 | class SharedQueue { 160 | public: 161 | std::atomic counter; 162 | std::array elements; 163 | }; 164 | 165 | template 166 | class Memory { 167 | public: 168 | explicit Memory(const std::string &name) : name_(name) { 169 | auto pid_set_size = sizeof(PIDSet); 170 | auto shared_queue_size = sizeof(SharedQueue); 171 | auto allocator_size = sizeof(AllocatorT); 172 | 173 | auto total_size = pid_set_size + shared_queue_size + allocator_size + 174 | buffer_size + 4 * GAP; 175 | bool new_segment = false; 176 | auto *base_address = 177 | create_memory_segment("/SHM_" + name, total_size, &new_segment); 178 | 179 | if (base_address == nullptr) { 180 | std::cerr << "Could not create/open shared memory segment.\n"; 181 | exit(1); 182 | } 183 | 184 | auto *pid_set_address = base_address; 185 | auto *shared_queue_address = pid_set_address + pid_set_size + GAP; 186 | auto *allocator_address = shared_queue_address + shared_queue_size + GAP; 187 | auto *buffer_address = allocator_address + allocator_size + GAP; 188 | 189 | if (new_segment) { 190 | pid_set_ = new (pid_set_address) PIDSet(); 191 | shared_queue_ = new (shared_queue_address) SharedQueue(); 192 | allocator_ = new (allocator_address) 193 | AllocatorT(buffer_address - allocator_address, buffer_size); 194 | 195 | pid_set_->insert(getpid()); 196 | init_shared_queue(); 197 | } else { 198 | /* 199 | * We do a cast first. So if the shared queue isn't init'd 200 | * in time, i.e., `init_shared_queue` is still running, 201 | * we need to wait a bit to ensure nothing crazy happens. 202 | * The exact time to sleep is just a guess: depends on file IO. 203 | * Currently: 10ms. 204 | */ 205 | pid_set_ = reinterpret_cast(pid_set_address); 206 | shared_queue_ = 207 | reinterpret_cast *>(shared_queue_address); 208 | allocator_ = reinterpret_cast(allocator_address); 209 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 210 | 211 | /* 212 | * Check if any of the participating PIDs are up and running. 213 | * If all participating PIDs are dead, reset the underlying memory 214 | * structures: 215 | * - Reset the allocator's underlying buffer 216 | * - Set the free, allocation indices to 0 217 | * - Reset the shared queue 218 | * - Set the counter to 0 219 | * - Set all underlying elements of queue to be empty 220 | * - Reset all mutexes/locks 221 | */ 222 | pid_set_->lock(); 223 | if (!pid_set_->any_alive()) { 224 | allocator_->lock_reset(); 225 | for (auto &elem : shared_queue_->elements) { 226 | elem.reset(); 227 | } 228 | shared_queue_->counter = 0; 229 | allocator_->reset(); 230 | } else { 231 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 232 | } 233 | pid_set_->unlock(); 234 | pid_set_->insert(getpid()); 235 | } 236 | } 237 | 238 | ~Memory() = default; 239 | 240 | void init_shared_queue() { 241 | /* 242 | * This is only called by the first process. It initializes the 243 | * counter to 0. 244 | */ 245 | shared_queue_->counter = 0; 246 | } 247 | 248 | size_t queue_size() const { return QUEUE_SIZE; } 249 | 250 | std::string name_; 251 | PIDSet *pid_set_; 252 | AllocatorT *allocator_; 253 | SharedQueue *shared_queue_; 254 | }; 255 | 256 | } // namespace shm::memory 257 | 258 | #endif // INCLUDE_SHADESMAR_MEMORY_MEMORY_H_ 259 | -------------------------------------------------------------------------------- /include/shadesmar/pubsub/publisher.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_PUBSUB_PUBLISHER_H_ 25 | #define INCLUDE_SHADESMAR_PUBSUB_PUBLISHER_H_ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "shadesmar/memory/copier.h" 35 | #include "shadesmar/pubsub/topic.h" 36 | 37 | namespace shm::pubsub { 38 | 39 | class Publisher { 40 | public: 41 | explicit Publisher(const std::string &topic_name); 42 | Publisher(const std::string &topic_name, 43 | std::shared_ptr copier); 44 | Publisher(const Publisher &) = delete; 45 | Publisher(Publisher &&); 46 | bool publish(void *data, size_t size); 47 | 48 | private: 49 | std::string topic_name_; 50 | std::unique_ptr topic_; 51 | }; 52 | 53 | Publisher::Publisher(const std::string &topic_name) : topic_name_(topic_name) { 54 | topic_ = std::make_unique(topic_name); 55 | } 56 | 57 | Publisher::Publisher(const std::string &topic_name, 58 | std::shared_ptr copier) 59 | : topic_name_(topic_name) { 60 | topic_ = std::make_unique(topic_name, copier); 61 | } 62 | 63 | Publisher::Publisher(Publisher &&other) { 64 | topic_name_ = other.topic_name_; 65 | topic_ = std::move(other.topic_); 66 | } 67 | 68 | bool Publisher::publish(void *data, size_t size) { 69 | memory::Memblock memblock(data, size); 70 | return topic_->write(memblock); 71 | } 72 | 73 | } // namespace shm::pubsub 74 | #endif // INCLUDE_SHADESMAR_PUBSUB_PUBLISHER_H_ 75 | -------------------------------------------------------------------------------- /include/shadesmar/pubsub/subscriber.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_PUBSUB_SUBSCRIBER_H_ 25 | #define INCLUDE_SHADESMAR_PUBSUB_SUBSCRIBER_H_ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "shadesmar/memory/copier.h" 37 | #include "shadesmar/pubsub/topic.h" 38 | 39 | namespace shm::pubsub { 40 | 41 | class Subscriber { 42 | public: 43 | Subscriber(const std::string &topic_name, 44 | std::function callback); 45 | Subscriber(const std::string &topic_name, 46 | std::function callback, 47 | std::shared_ptr copier); 48 | 49 | Subscriber(const Subscriber &other) = delete; 50 | 51 | Subscriber(Subscriber &&other); 52 | 53 | memory::Memblock get_message(); 54 | void spin_once(); 55 | void spin(); 56 | void stop(); 57 | 58 | std::atomic counter_{0}; 59 | 60 | private: 61 | std::atomic_bool running_{false}; 62 | std::function callback_; 63 | std::string topic_name_; 64 | std::unique_ptr topic_; 65 | }; 66 | 67 | Subscriber::Subscriber(const std::string &topic_name, 68 | std::function callback) 69 | : topic_name_(topic_name), callback_(std::move(callback)) { 70 | topic_ = std::make_unique(topic_name_); 71 | } 72 | 73 | Subscriber::Subscriber(const std::string &topic_name, 74 | std::function callback, 75 | std::shared_ptr copier) 76 | : topic_name_(topic_name), callback_(std::move(callback)) { 77 | topic_ = std::make_unique(topic_name_, copier); 78 | } 79 | 80 | Subscriber::Subscriber(Subscriber &&other) { 81 | callback_ = std::move(other.callback_); 82 | topic_ = std::move(other.topic_); 83 | } 84 | 85 | memory::Memblock Subscriber::get_message() { 86 | /* 87 | * topic's `counter` must be strictly greater than counter. 88 | * If they're equal, there have been no new writes. 89 | * If subscriber's counter is too far ahead, we need to catch up. 90 | */ 91 | if (topic_->counter() <= counter_) { 92 | // no new messages 93 | return memory::Memblock(); 94 | } 95 | 96 | if (topic_->counter() - counter_ >= topic_->queue_size()) { 97 | /* 98 | * Why is the check >= (not >)? This is because in topic's 99 | * `write` we do an `inc` at the end, so the write head 100 | * (topic_->counter()) is currently under-write. 101 | * 102 | * If we have fallen behind by the size of the queue 103 | * in the case of overlap, we go to last existing 104 | * element in the queue. 105 | * 106 | * 107 | * Rather than going to the last valid location: 108 | * topic_->counter() - topic_->queue_size() 109 | * We move to a more optimistic location (see `jumpahead`), to prevent 110 | * hitting a case of always trying to keep up with the publisher. 111 | */ 112 | counter_ = jumpahead(topic_->counter(), topic_->queue_size()); 113 | } 114 | 115 | memory::Memblock memblock; 116 | memblock.free = true; 117 | 118 | if (!topic_->read(&memblock, &counter_)) { 119 | return memory::Memblock(); 120 | } 121 | 122 | return memblock; 123 | } 124 | 125 | // Not thread-safe. Should be called from a single thread. 126 | void Subscriber::spin_once() { 127 | memory::Memblock memblock = get_message(); 128 | 129 | if (memblock.is_empty()) { 130 | return; 131 | } 132 | 133 | callback_(&memblock); 134 | counter_++; 135 | 136 | if (memblock.free) { 137 | topic_->copier()->dealloc(memblock.ptr); 138 | memblock.ptr = nullptr; 139 | memblock.size = 0; 140 | } 141 | } 142 | 143 | void Subscriber::spin() { 144 | running_ = true; 145 | while (running_.load()) { 146 | spin_once(); 147 | } 148 | } 149 | 150 | void Subscriber::stop() { running_ = false; } 151 | 152 | } // namespace shm::pubsub 153 | #endif // INCLUDE_SHADESMAR_PUBSUB_SUBSCRIBER_H_ 154 | -------------------------------------------------------------------------------- /include/shadesmar/pubsub/topic.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_PUBSUB_TOPIC_H_ 25 | #define INCLUDE_SHADESMAR_PUBSUB_TOPIC_H_ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "shadesmar/concurrency/scope.h" 34 | #include "shadesmar/macros.h" 35 | #include "shadesmar/memory/allocator.h" 36 | #include "shadesmar/memory/copier.h" 37 | #include "shadesmar/memory/memory.h" 38 | 39 | namespace shm::pubsub { 40 | 41 | // The logic for optimistically jumping ahead, if the current read 42 | // logic has fallen behind (circular wrap around). `counter` is the 43 | // current write head location. 44 | inline uint32_t jumpahead(uint32_t counter, uint32_t queue_size) { 45 | return counter - queue_size / 2; 46 | } 47 | 48 | template 49 | struct TopicElemT { 50 | memory::Element msg; 51 | LockT mutex; 52 | 53 | TopicElemT() : msg(), mutex() {} 54 | 55 | TopicElemT(const TopicElemT &topic_elem) { 56 | msg = topic_elem.msg; 57 | mutex = topic_elem.mutex; 58 | } 59 | 60 | void reset() { 61 | msg.reset(); 62 | mutex.reset(); 63 | } 64 | }; 65 | 66 | using LockType = concurrent::PthreadReadWriteLock; 67 | 68 | class Topic { 69 | using TopicElem = TopicElemT; 70 | 71 | template 72 | using Scope = concurrent::ScopeGuard; 73 | 74 | public: 75 | explicit Topic(const std::string &topic) 76 | : Topic(topic, std::make_shared()) {} 77 | Topic(const std::string &topic, std::shared_ptr copier) 78 | : memory_(topic) { 79 | if (copier == nullptr) { 80 | copier = std::make_shared(); 81 | } 82 | copier_ = copier; 83 | } 84 | 85 | ~Topic() = default; 86 | 87 | bool write(memory::Memblock memblock) { 88 | /* 89 | * Writes always happen at the head of the circular queue, the 90 | * head is atomically incremented to prevent any race across 91 | * processes. The head of the queue is stored as a counter 92 | * on shared memory. 93 | */ 94 | if (memblock.size > memory_.allocator_->get_free_memory()) { 95 | std::cerr << "Increase buffer_size" << std::endl; 96 | return false; 97 | } 98 | uint32_t q_pos = counter() & (queue_size() - 1); 99 | TopicElem *elem = &(memory_.shared_queue_->elements[q_pos]); 100 | 101 | /* 102 | * Code path: 103 | * 1. Allocate shared memory buffer `new_address` 104 | * 2. Copy msg data to `new_address` 105 | * 3. Acquire exclusive lock 106 | * 4. Complex "swap" of old and new fields 107 | * 5. Release exclusive lock 108 | * 6. If old buffer is empty, deallocate it 109 | */ 110 | 111 | uint8_t *new_address = memory_.allocator_->alloc(memblock.size); 112 | if (new_address == nullptr) { 113 | return false; 114 | } 115 | 116 | copier_->user_to_shm(new_address, memblock.ptr, memblock.size); 117 | 118 | uint8_t *old_address = nullptr; 119 | { 120 | /* 121 | * This locked block should *only* contain accesses 122 | * to `elem`, any other expensive compute that doesn't 123 | * include `elem` can be put outside this block. 124 | */ 125 | Scope _(&elem->mutex); 126 | if (!elem->msg.empty) { 127 | old_address = 128 | memory_.allocator_->handle_to_ptr(elem->msg.address_handle); 129 | } 130 | elem->msg.address_handle = 131 | memory_.allocator_->ptr_to_handle(new_address); 132 | elem->msg.size = memblock.size; 133 | elem->msg.empty = false; 134 | } 135 | 136 | while (!memory_.allocator_->free(old_address)) { 137 | std::this_thread::sleep_for(std::chrono::microseconds(100)); 138 | } 139 | inc_counter(); 140 | return true; 141 | } 142 | 143 | /* 144 | * Reads aren't like writes in one major way: writes don't require 145 | * any information about which position in the queue to write to. It 146 | * defaults to the head of the queue. Reads requires an explicit 147 | * position in the queue to read(`pos`). We use this position 148 | * argument since we need to support multiple subscribers each 149 | * reading at their own pace. Between picking the element at pos to 150 | * read from and acquiring a read lock, the publisher may write a new 151 | * value at pos. In this case, we implement a slow path to jump ahead. 152 | */ 153 | 154 | bool read(memory::Memblock *memblock, std::atomic *pos) { 155 | TopicElem *elem = 156 | &(memory_.shared_queue_->elements[*pos & (queue_size() - 1)]); 157 | 158 | /* 159 | * Code path (without the slow path for lag): 160 | * 1. Acquire sharable lock 161 | * 2. Check for emptiness 162 | * 3. Copy from shared memory to input param `msg` 163 | * 4. Release sharable lock 164 | */ 165 | Scope _(&elem->mutex); 166 | 167 | // Using a lambda for this reduced throughput. 168 | #define MOVE_ELEM(_elem) \ 169 | if (_elem->msg.empty) { \ 170 | return false; \ 171 | } \ 172 | auto *dst = memory_.allocator_->handle_to_ptr(_elem->msg.address_handle); \ 173 | memblock->size = _elem->msg.size; \ 174 | memblock->ptr = copier_->alloc(memblock->size); \ 175 | copier_->shm_to_user(memblock->ptr, dst, memblock->size); 176 | 177 | if (queue_size() > counter() - *pos) { 178 | // Fast path. 179 | MOVE_ELEM(elem); 180 | return true; 181 | } 182 | 183 | // See comment in `pubsub/subscriber.h`, in function `get_message()` for 184 | // more info. *pos is outdated, the publisher has already written here 185 | // before the reader lock was held. Jump ahead optimisically. 186 | // 187 | // Q: Why no lock on `next_best_elem`? 188 | // A: `elem` is behind `next_best_elem`. With a lock on the former, the 189 | // publisher cannot cross `elem` to get to `next_best_elem`. 190 | // 191 | // Q: Why is the jump ahead implemented again in `get_message()`? 192 | // A: `get_message()` can jump ahead outside of holding a lock. If a 193 | // lag can be detected there, it is more performant. This is a slow 194 | // path under a read lock. 195 | *pos = jumpahead(counter(), queue_size()); 196 | TopicElem *next_best_elem = 197 | &(memory_.shared_queue_->elements[*pos & (queue_size() - 1)]); 198 | MOVE_ELEM(next_best_elem); 199 | return true; 200 | #undef MOVE_ELEM 201 | } 202 | 203 | inline __attribute__((always_inline)) void inc_counter() { 204 | memory_.shared_queue_->counter++; 205 | } 206 | 207 | inline __attribute__((always_inline)) uint32_t counter() const { 208 | return memory_.shared_queue_->counter.load(); 209 | } 210 | 211 | size_t queue_size() const { return memory_.queue_size(); } 212 | 213 | inline std::shared_ptr copier() const { return copier_; } 214 | 215 | private: 216 | memory::Memory memory_; 217 | std::shared_ptr copier_; 218 | }; 219 | } // namespace shm::pubsub 220 | #endif // INCLUDE_SHADESMAR_PUBSUB_TOPIC_H_ 221 | -------------------------------------------------------------------------------- /include/shadesmar/rpc/channel.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2021 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_RPC_CHANNEL_H_ 25 | #define INCLUDE_SHADESMAR_RPC_CHANNEL_H_ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include "shadesmar/concurrency/cond_var.h" 32 | #include "shadesmar/concurrency/lock.h" 33 | #include "shadesmar/concurrency/scope.h" 34 | #include "shadesmar/macros.h" 35 | #include "shadesmar/memory/allocator.h" 36 | #include "shadesmar/memory/copier.h" 37 | #include "shadesmar/memory/double_allocator.h" 38 | #include "shadesmar/memory/memory.h" 39 | 40 | namespace shm::rpc { 41 | 42 | struct ChannelElem { 43 | memory::Element req; 44 | memory::Element resp; 45 | concurrent::PthreadWriteLock mutex; 46 | concurrent::CondVar cond_var; 47 | 48 | ChannelElem() : req(), resp(), mutex(), cond_var() {} 49 | 50 | ChannelElem(const ChannelElem &channel_elem) { 51 | mutex = channel_elem.mutex; 52 | cond_var = channel_elem.cond_var; 53 | req = channel_elem.req; 54 | resp = channel_elem.resp; 55 | } 56 | 57 | void reset() { 58 | req.reset(); 59 | resp.reset(); 60 | mutex.reset(); 61 | cond_var.reset(); 62 | } 63 | }; 64 | 65 | // clients are calling RPCs provided by servers. 66 | class Channel { 67 | using Scope = concurrent::ScopeGuard; 69 | 70 | public: 71 | explicit Channel(const std::string &channel) 72 | : Channel(channel, std::make_shared()) {} 73 | Channel(const std::string &channel, std::shared_ptr copier) 74 | : memory_(channel) { 75 | if (copier == nullptr) { 76 | copier = std::make_shared(); 77 | } 78 | copier_ = copier; 79 | } 80 | 81 | ~Channel() = default; 82 | 83 | bool write_client(memory::Memblock memblock, uint32_t *pos) { 84 | if (memblock.size > memory_.allocator_->req.get_free_memory()) { 85 | std::cerr << "Increase buffer_size" << std::endl; 86 | return false; 87 | } 88 | *pos = counter(); 89 | auto q_pos = *pos & (queue_size() - 1); 90 | ChannelElem *elem = &(memory_.shared_queue_->elements[q_pos]); 91 | 92 | { 93 | Scope _(&elem->mutex); 94 | if (!elem->req.empty) { 95 | std::cerr << "Queue is full, try again." << std::endl; 96 | return false; 97 | } 98 | inc_counter(); // move this after successful alloc? It'll prevent empty 99 | // req cells. 100 | elem->req.empty = false; 101 | } 102 | 103 | // With the code-block, when a thread reaches here, it is the only one that 104 | // can access `elem`. No need for lock. 105 | 106 | uint8_t *new_address = memory_.allocator_->req.alloc(memblock.size); 107 | if (new_address == nullptr) { 108 | elem->req.reset(); 109 | return false; 110 | } 111 | 112 | copier_->user_to_shm(new_address, memblock.ptr, memblock.size); 113 | { 114 | elem->req.address_handle = 115 | memory_.allocator_->req.ptr_to_handle(new_address); 116 | elem->req.size = memblock.size; 117 | } 118 | return true; 119 | } 120 | 121 | bool read_client(uint32_t pos, memory::Memblock *memblock) { 122 | uint32_t q_pos = pos & (queue_size() - 1); 123 | ChannelElem *elem = &(memory_.shared_queue_->elements[q_pos]); 124 | 125 | Scope _(&elem->mutex); 126 | while (elem->resp.empty) { 127 | elem->cond_var.wait(&elem->mutex); 128 | } 129 | 130 | auto clean_up = [this](ChannelElem *elem) { 131 | if (elem->req.size != 0) { 132 | auto address = 133 | memory_.allocator_->req.handle_to_ptr(elem->req.address_handle); 134 | memory_.allocator_->req.free(address); 135 | } 136 | elem->resp.reset(); 137 | elem->req.reset(); 138 | }; 139 | 140 | if (elem->resp.address_handle == 0) { 141 | clean_up(elem); 142 | return false; 143 | } 144 | 145 | uint8_t *address = 146 | memory_.allocator_->resp.handle_to_ptr(elem->resp.address_handle); 147 | memblock->size = elem->resp.size; 148 | memblock->ptr = copier_->alloc(memblock->size); 149 | copier_->shm_to_user(memblock->ptr, address, memblock->size); 150 | clean_up(elem); 151 | return true; 152 | } 153 | 154 | bool write_server(memory::Memblock memblock, uint32_t pos) { 155 | uint32_t q_pos = pos & (queue_size() - 1); 156 | ChannelElem *elem = &(memory_.shared_queue_->elements[q_pos]); 157 | 158 | auto signal_error = [](ChannelElem *elem) { 159 | // This is how we convey an error: We mark that the result is written, 160 | // so `read_client` will still read this value, but since the address 161 | // handle for the result is 0, it can't read from buffer. 162 | Scope _(&elem->mutex); 163 | elem->resp.reset(); 164 | elem->resp.empty = false; 165 | elem->cond_var.signal(); 166 | }; 167 | 168 | if (memblock.size > memory_.allocator_->resp.get_free_memory()) { 169 | std::cerr << "Increase buffer_size" << std::endl; 170 | signal_error(elem); 171 | return false; 172 | } 173 | 174 | uint8_t *resp_address = memory_.allocator_->resp.alloc(memblock.size); 175 | if (resp_address == nullptr) { 176 | std::cerr << "Failed to alloc resp buffer" << std::endl; 177 | signal_error(elem); 178 | return false; 179 | } 180 | 181 | copier_->user_to_shm(resp_address, memblock.ptr, memblock.size); 182 | Scope _(&elem->mutex); 183 | elem->resp.empty = false; 184 | elem->resp.address_handle = 185 | memory_.allocator_->resp.ptr_to_handle(resp_address); 186 | elem->resp.size = memblock.size; 187 | elem->cond_var.signal(); 188 | return true; 189 | } 190 | 191 | bool read_server(uint32_t pos, memory::Memblock *memblock) { 192 | uint32_t q_pos = pos & (queue_size() - 1); 193 | ChannelElem *elem = &(memory_.shared_queue_->elements[q_pos]); 194 | Scope _(&elem->mutex); 195 | 196 | if (elem->req.empty) { 197 | return false; 198 | } 199 | uint8_t *address = 200 | memory_.allocator_->req.handle_to_ptr(elem->req.address_handle); 201 | memblock->size = elem->req.size; 202 | memblock->ptr = copier_->alloc(memblock->size); 203 | copier_->shm_to_user(memblock->ptr, address, memblock->size); 204 | return true; 205 | } 206 | 207 | inline __attribute__((always_inline)) void inc_counter() { 208 | memory_.shared_queue_->counter++; 209 | } 210 | 211 | inline __attribute__((always_inline)) uint32_t counter() const { 212 | return memory_.shared_queue_->counter.load(); 213 | } 214 | 215 | size_t queue_size() const { return memory_.queue_size(); } 216 | 217 | inline std::shared_ptr copier() const { return copier_; } 218 | 219 | private: 220 | memory::Memory memory_; 221 | std::shared_ptr copier_; 222 | }; 223 | 224 | } // namespace shm::rpc 225 | 226 | #endif // INCLUDE_SHADESMAR_RPC_CHANNEL_H_ 227 | -------------------------------------------------------------------------------- /include/shadesmar/rpc/client.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2021 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_RPC_CLIENT_H_ 25 | #define INCLUDE_SHADESMAR_RPC_CLIENT_H_ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include "shadesmar/memory/copier.h" 32 | #include "shadesmar/memory/memory.h" 33 | #include "shadesmar/rpc/channel.h" 34 | 35 | namespace shm::rpc { 36 | class Client { 37 | public: 38 | explicit Client(const std::string &channel_name); 39 | Client(const std::string &channel_name, 40 | std::shared_ptr copier); 41 | Client(const Client &) = delete; 42 | Client(Client &&); 43 | 44 | bool call(const memory::Memblock &req, memory::Memblock *resp) const; 45 | bool send(const memory::Memblock &req, uint32_t *pos) const; 46 | bool recv(uint32_t pos, memory::Memblock *resp) const; 47 | void free_resp(memory::Memblock *resp) const; 48 | 49 | private: 50 | std::string channel_name_; 51 | std::unique_ptr channel_; 52 | }; 53 | 54 | Client::Client(const std::string &channel_name) : channel_name_(channel_name) { 55 | channel_ = std::make_unique(channel_name); 56 | } 57 | 58 | Client::Client(const std::string &channel_name, 59 | std::shared_ptr copier) 60 | : channel_name_(channel_name) { 61 | channel_ = std::make_unique(channel_name, copier); 62 | } 63 | 64 | Client::Client(Client &&other) { 65 | channel_name_ = other.channel_name_; 66 | channel_ = std::move(other.channel_); 67 | } 68 | 69 | bool Client::call(const memory::Memblock &req, memory::Memblock *resp) const { 70 | uint32_t pos; 71 | 72 | bool success = send(req, &pos); 73 | if (!success) return success; 74 | return recv(pos, resp); 75 | } 76 | 77 | bool Client::send(const memory::Memblock &req, uint32_t *pos) const { 78 | return channel_->write_client(req, pos); 79 | } 80 | 81 | bool Client::recv(uint32_t pos, memory::Memblock *resp) const { 82 | return channel_->read_client(pos, resp); 83 | } 84 | 85 | void Client::free_resp(memory::Memblock *resp) const { 86 | channel_->copier()->dealloc(resp->ptr); 87 | resp->ptr = nullptr; 88 | resp->size = 0; 89 | } 90 | 91 | } // namespace shm::rpc 92 | 93 | #endif // INCLUDE_SHADESMAR_RPC_CLIENT_H_ 94 | -------------------------------------------------------------------------------- /include/shadesmar/rpc/server.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2021 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_RPC_SERVER_H_ 25 | #define INCLUDE_SHADESMAR_RPC_SERVER_H_ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "shadesmar/memory/memory.h" 34 | #include "shadesmar/rpc/channel.h" 35 | 36 | namespace shm::rpc { 37 | 38 | using Callback = 39 | std::function; 40 | 41 | using Cleanup = std::function; 42 | 43 | inline Cleanup empty_cleanup() { 44 | return [](memory::Memblock* resp) { 45 | resp->ptr = nullptr; 46 | resp->size = 0; 47 | }; 48 | } 49 | 50 | class Server { 51 | public: 52 | Server(const std::string& channel_name, Callback cb); 53 | Server(const std::string& channel_name, Callback cb, Cleanup cln); 54 | Server(const std::string& channel_name, Callback cb, Cleanup cln, 55 | std::shared_ptr copier); 56 | Server(const Server& other) = delete; 57 | Server(Server&& other); 58 | 59 | bool serve_once(); 60 | void serve(); 61 | void stop(); 62 | 63 | private: 64 | bool process(uint32_t pos) const; 65 | void cleanup_req(memory::Memblock*) const; 66 | std::atomic_uint32_t pos_{0}; 67 | std::atomic_bool running_{false}; 68 | Callback callback_; 69 | Cleanup cleanup_; 70 | std::string channel_name_; 71 | std::unique_ptr channel_; 72 | }; 73 | 74 | Server::Server(const std::string& channel_name, Callback cb) 75 | : channel_name_(channel_name), 76 | callback_(std::move(cb)), 77 | cleanup_(std::move(empty_cleanup())) { 78 | channel_ = std::make_unique(channel_name); 79 | } 80 | 81 | Server::Server(const std::string& channel_name, Callback cb, Cleanup cleanup) 82 | : channel_name_(channel_name), 83 | callback_(std::move(cb)), 84 | cleanup_(cleanup) { 85 | channel_ = std::make_unique(channel_name); 86 | } 87 | 88 | Server::Server(const std::string& channel_name, Callback cb, Cleanup cleanup, 89 | std::shared_ptr copier) 90 | : channel_name_(channel_name), 91 | callback_(std::move(cb)), 92 | cleanup_(cleanup) { 93 | channel_ = std::make_unique(channel_name, copier); 94 | } 95 | 96 | Server::Server(Server&& other) { 97 | callback_ = std::move(other.callback_); 98 | cleanup_ = std::move(other.cleanup_); 99 | channel_ = std::move(other.channel_); 100 | } 101 | 102 | void Server::cleanup_req(memory::Memblock* req) const { 103 | // TODO(squadrick): Move this into Channel. 104 | if (req->is_empty()) return; 105 | channel_->copier()->dealloc(req->ptr); 106 | *req = memory::Memblock(nullptr, 0); 107 | } 108 | 109 | bool Server::process(uint32_t pos) const { 110 | memory::Memblock req, resp; 111 | 112 | bool success = channel_->read_server(pos, &req); 113 | if (!success) { 114 | cleanup_req(&req); 115 | return success; 116 | } 117 | bool cbSuccess = callback_(req, &resp); 118 | if (!cbSuccess) { 119 | cleanup_(&resp); 120 | resp = memory::Memblock(nullptr, 0); 121 | cleanup_req(&req); 122 | } 123 | success = channel_->write_server(resp, pos); 124 | cleanup_req(&req); 125 | cleanup_(&resp); 126 | return success; 127 | } 128 | 129 | bool Server::serve_once() { 130 | bool success = process(pos_.fetch_add(1)); 131 | return success; 132 | } 133 | 134 | void Server::serve() { 135 | running_ = true; 136 | while (running_.load()) { 137 | serve_once(); 138 | } 139 | } 140 | 141 | void Server::stop() { running_ = false; } 142 | 143 | } // namespace shm::rpc 144 | 145 | #endif // INCLUDE_SHADESMAR_RPC_SERVER_H_ 146 | -------------------------------------------------------------------------------- /include/shadesmar/stats.h: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifndef INCLUDE_SHADESMAR_STATS_H_ 25 | #define INCLUDE_SHADESMAR_STATS_H_ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | namespace shm::stats { 32 | 33 | class Welford { 34 | public: 35 | Welford() { clear(); } 36 | 37 | void clear() { 38 | n = 0; 39 | old_mean = new_mean = 0.0; 40 | old_dev = new_dev = 0.0; 41 | } 42 | 43 | void add(double value) { 44 | n++; 45 | if (n == 1) { 46 | old_mean = new_mean = value; 47 | old_dev = 0.0; 48 | } else { 49 | new_mean = old_mean + (value - old_mean) / n; 50 | new_dev = old_dev + (value - old_mean) * (value - new_mean); 51 | 52 | old_mean = new_mean; 53 | old_dev = new_dev; 54 | } 55 | } 56 | 57 | size_t size() const { return n; } 58 | 59 | double mean() const { 60 | if (n > 0) { 61 | return new_mean; 62 | } 63 | return 0.0; 64 | } 65 | 66 | double variance() const { 67 | if (n > 1) { 68 | return new_dev / (n - 1); 69 | } 70 | return 0.0; 71 | } 72 | 73 | double std_dev() const { return std::sqrt(variance()); } 74 | 75 | friend std::ostream& operator<<(std::ostream& o, const Welford& w); 76 | 77 | private: 78 | size_t n{}; 79 | double old_mean{}, new_mean{}, old_dev{}, new_dev{}; 80 | }; 81 | 82 | std::ostream& operator<<(std::ostream& o, const Welford& w) { 83 | return o << w.mean() << " ± " << w.std_dev() << " (" << w.size() << ")"; 84 | } 85 | 86 | } // namespace shm::stats 87 | 88 | #endif // INCLUDE_SHADESMAR_STATS_H_ 89 | -------------------------------------------------------------------------------- /release/shadesmar.h: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2019 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | */ 24 | 25 | /* 26 | THIS SINGLE HEADER FILE WAS AUTO-GENERATED USING `simul/simul.py`. 27 | DO NOT MAKE CHANGES HERE. 28 | 29 | GENERATION TIME: 2022-08-15 10:40:23.622183 UTC 30 | */ 31 | 32 | #ifndef SHADESMAR_SINGLE_H_ 33 | #define SHADESMAR_SINGLE_H_ 34 | namespace shm::concurrent { 35 | enum ExlOrShr { EXCLUSIVE, SHARED }; 36 | template 37 | class ScopeGuard; 38 | template 39 | class ScopeGuard { 40 | public: 41 | explicit ScopeGuard(LockT *lck) : lck_(lck) { 42 | if (lck_ != nullptr) { 43 | lck_->lock(); 44 | } 45 | } 46 | ~ScopeGuard() { 47 | if (lck_ != nullptr) { 48 | lck_->unlock(); 49 | lck_ = nullptr; 50 | } 51 | } 52 | private: 53 | LockT *lck_; 54 | }; 55 | template 56 | class ScopeGuard { 57 | public: 58 | explicit ScopeGuard(LockT *lck) : lck_(lck) { lck_->lock_sharable(); } 59 | ~ScopeGuard() { 60 | if (lck_ != nullptr) { 61 | lck_->unlock_sharable(); 62 | lck_ = nullptr; 63 | } 64 | } 65 | private: 66 | LockT *lck_; 67 | }; 68 | } 69 | #include 70 | #include 71 | #include 72 | namespace shm::concurrent { 73 | class PthreadWriteLock { 74 | public: 75 | PthreadWriteLock(); 76 | ~PthreadWriteLock(); 77 | void lock(); 78 | bool try_lock(); 79 | void unlock(); 80 | void reset(); 81 | pthread_mutex_t *get_mutex() { return &mutex; } 82 | private: 83 | pthread_mutex_t mutex{}; 84 | pthread_mutexattr_t attr{}; 85 | }; 86 | PthreadWriteLock::PthreadWriteLock() { 87 | pthread_mutexattr_init(&attr); 88 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); 89 | pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); 90 | #ifdef __linux__ 91 | pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); 92 | #endif 93 | pthread_mutex_init(&mutex, &attr); 94 | } 95 | PthreadWriteLock::~PthreadWriteLock() { 96 | pthread_mutexattr_destroy(&attr); 97 | pthread_mutex_destroy(&mutex); 98 | } 99 | void PthreadWriteLock::lock() { 100 | pthread_mutex_lock(&mutex); 101 | #ifdef __linux__ 102 | if (errno == EOWNERDEAD) { 103 | std::cerr << "Previous owner of mutex was dead." << std::endl; 104 | } 105 | #endif 106 | } 107 | bool PthreadWriteLock::try_lock() { return pthread_mutex_trylock(&mutex); } 108 | void PthreadWriteLock::unlock() { pthread_mutex_unlock(&mutex); } 109 | void PthreadWriteLock::reset() { 110 | pthread_mutex_destroy(&mutex); 111 | pthread_mutex_init(&mutex, &attr); 112 | } 113 | } 114 | #include 115 | namespace shm::concurrent { 116 | class CondVar { 117 | public: 118 | CondVar(); 119 | ~CondVar(); 120 | void wait(PthreadWriteLock *lock); 121 | void signal(); 122 | void reset(); 123 | private: 124 | pthread_condattr_t attr; 125 | pthread_cond_t cond; 126 | }; 127 | CondVar::CondVar() { 128 | pthread_condattr_init(&attr); 129 | pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); 130 | pthread_cond_init(&cond, &attr); 131 | } 132 | CondVar::~CondVar() { 133 | pthread_condattr_destroy(&attr); 134 | pthread_cond_destroy(&cond); 135 | } 136 | void CondVar::wait(PthreadWriteLock *lock) { 137 | pthread_cond_wait(&cond, lock->get_mutex()); 138 | } 139 | void CondVar::signal() { pthread_cond_signal(&cond); } 140 | void CondVar::reset() { 141 | pthread_cond_destroy(&cond); 142 | pthread_cond_init(&cond, &attr); 143 | } 144 | } 145 | #include 146 | #include 147 | namespace shm::memory { 148 | class Copier { 149 | public: 150 | virtual ~Copier() = default; 151 | virtual void *alloc(size_t) = 0; 152 | virtual void dealloc(void *) = 0; 153 | virtual void shm_to_user(void *, void *, size_t) = 0; 154 | virtual void user_to_shm(void *, void *, size_t) = 0; 155 | }; 156 | class DefaultCopier : public Copier { 157 | public: 158 | using PtrT = uint8_t; 159 | void *alloc(size_t size) override { return malloc(size); } 160 | void dealloc(void *ptr) override { free(ptr); } 161 | void shm_to_user(void *dst, void *src, size_t size) override { 162 | std::memcpy(dst, src, size); 163 | } 164 | void user_to_shm(void *dst, void *src, size_t size) override { 165 | std::memcpy(dst, src, size); 166 | } 167 | }; 168 | } 169 | #include 170 | #include 171 | #include 172 | namespace shm::stats { 173 | class Welford { 174 | public: 175 | Welford() { clear(); } 176 | void clear() { 177 | n = 0; 178 | old_mean = new_mean = 0.0; 179 | old_dev = new_dev = 0.0; 180 | } 181 | void add(double value) { 182 | n++; 183 | if (n == 1) { 184 | old_mean = new_mean = value; 185 | old_dev = 0.0; 186 | } else { 187 | new_mean = old_mean + (value - old_mean) / n; 188 | new_dev = old_dev + (value - old_mean) * (value - new_mean); 189 | old_mean = new_mean; 190 | old_dev = new_dev; 191 | } 192 | } 193 | size_t size() const { return n; } 194 | double mean() const { 195 | if (n > 0) { 196 | return new_mean; 197 | } 198 | return 0.0; 199 | } 200 | double variance() const { 201 | if (n > 1) { 202 | return new_dev / (n - 1); 203 | } 204 | return 0.0; 205 | } 206 | double std_dev() const { return std::sqrt(variance()); } 207 | friend std::ostream& operator<<(std::ostream& o, const Welford& w); 208 | private: 209 | size_t n{}; 210 | double old_mean{}, new_mean{}, old_dev{}, new_dev{}; 211 | }; 212 | std::ostream& operator<<(std::ostream& o, const Welford& w) { 213 | return o << w.mean() << " ± " << w.std_dev() << " (" << w.size() << ")"; 214 | } 215 | } 216 | #include 217 | #include 218 | #include 219 | #include 220 | #define TIMESCALE std::chrono::microseconds 221 | #define TIMESCALE_COUNT 1e6 222 | #define TIMESCALE_NAME "us" 223 | namespace shm { 224 | uint64_t current_time() { 225 | auto time_since_epoch = std::chrono::system_clock::now().time_since_epoch(); 226 | auto casted_time = std::chrono::duration_cast(time_since_epoch); 227 | return casted_time.count(); 228 | } 229 | } 230 | inline bool proc_dead(__pid_t proc) { 231 | if (proc == 0) { 232 | return false; 233 | } 234 | std::string pid_path = "/proc/" + std::to_string(proc); 235 | struct stat sts {}; 236 | return (stat(pid_path.c_str(), &sts) == -1 && errno == ENOENT); 237 | } 238 | #include 239 | #include 240 | #include 241 | namespace shm::concurrent { 242 | template 243 | class LocklessSet { 244 | public: 245 | LocklessSet(); 246 | LocklessSet &operator=(const LocklessSet &); 247 | bool insert(uint32_t elem); 248 | bool remove(uint32_t elem); 249 | std::array array_ = {}; 250 | }; 251 | template 252 | LocklessSet::LocklessSet() = default; 253 | template 254 | LocklessSet &LocklessSet::operator=(const LocklessSet &set) { 255 | for (uint32_t idx = 0; idx < Size; ++idx) { 256 | array_[idx].store(set.array_[idx].load()); 257 | } 258 | return *this; 259 | } 260 | template 261 | bool LocklessSet::insert(uint32_t elem) { 262 | for (uint32_t idx = 0; idx < Size; ++idx) { 263 | auto probedElem = array_[idx].load(); 264 | if (probedElem != elem) { 265 | if (probedElem != 0) { 266 | continue; 267 | } 268 | uint32_t exp = 0; 269 | if (array_[idx].compare_exchange_strong(exp, elem)) { 270 | return true; 271 | } else { 272 | continue; 273 | } 274 | } 275 | return false; 276 | } 277 | return false; 278 | } 279 | template 280 | bool LocklessSet::remove(uint32_t elem) { 281 | for (uint32_t idx = 0; idx < Size; ++idx) { 282 | auto probedElem = array_[idx].load(); 283 | if (probedElem == elem) { 284 | return array_[idx].compare_exchange_strong(elem, 0); 285 | } 286 | } 287 | return false; 288 | } 289 | } 290 | #include 291 | namespace shm::concurrent { 292 | class PthreadReadWriteLock { 293 | public: 294 | PthreadReadWriteLock(); 295 | ~PthreadReadWriteLock(); 296 | void lock(); 297 | bool try_lock(); 298 | void unlock(); 299 | void lock_sharable(); 300 | bool try_lock_sharable(); 301 | void unlock_sharable(); 302 | void reset(); 303 | private: 304 | pthread_rwlock_t rwlock{}; 305 | pthread_rwlockattr_t attr{}; 306 | }; 307 | PthreadReadWriteLock::PthreadReadWriteLock() { 308 | pthread_rwlockattr_init(&attr); 309 | pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); 310 | pthread_rwlock_init(&rwlock, &attr); 311 | } 312 | PthreadReadWriteLock::~PthreadReadWriteLock() { 313 | pthread_rwlockattr_destroy(&attr); 314 | pthread_rwlock_destroy(&rwlock); 315 | } 316 | void PthreadReadWriteLock::lock() { pthread_rwlock_wrlock(&rwlock); } 317 | bool PthreadReadWriteLock::try_lock() { 318 | return (!pthread_rwlock_trywrlock(&rwlock)); 319 | } 320 | void PthreadReadWriteLock::unlock() { pthread_rwlock_unlock(&rwlock); } 321 | void PthreadReadWriteLock::lock_sharable() { pthread_rwlock_rdlock(&rwlock); } 322 | bool PthreadReadWriteLock::try_lock_sharable() { 323 | return (!pthread_rwlock_tryrdlock(&rwlock)); 324 | } 325 | void PthreadReadWriteLock::unlock_sharable() { 326 | pthread_rwlock_unlock(&rwlock); 327 | } 328 | void PthreadReadWriteLock::reset() { 329 | pthread_rwlock_destroy(&rwlock); 330 | pthread_rwlock_init(&rwlock, &attr); 331 | } 332 | } 333 | #include 334 | #include 335 | #include 336 | #include 337 | #include 338 | namespace shm::concurrent { 339 | class RobustLock { 340 | public: 341 | RobustLock(); 342 | RobustLock(const RobustLock &); 343 | ~RobustLock(); 344 | void lock(); 345 | bool try_lock(); 346 | void unlock(); 347 | void lock_sharable(); 348 | bool try_lock_sharable(); 349 | void unlock_sharable(); 350 | void reset(); 351 | private: 352 | void prune_readers(); 353 | PthreadReadWriteLock mutex_; 354 | std::atomic<__pid_t> exclusive_owner{0}; 355 | LocklessSet<8> shared_owners; 356 | }; 357 | RobustLock::RobustLock() = default; 358 | RobustLock::RobustLock(const RobustLock &lock) { 359 | mutex_ = lock.mutex_; 360 | exclusive_owner.store(lock.exclusive_owner.load()); 361 | shared_owners = lock.shared_owners; 362 | } 363 | RobustLock::~RobustLock() { exclusive_owner = 0; } 364 | void RobustLock::lock() { 365 | while (!mutex_.try_lock()) { 366 | if (exclusive_owner.load() != 0) { 367 | auto ex_proc = exclusive_owner.load(); 368 | if (proc_dead(ex_proc)) { 369 | if (exclusive_owner.compare_exchange_strong(ex_proc, 0)) { 370 | mutex_.unlock(); 371 | continue; 372 | } 373 | } 374 | } else { 375 | prune_readers(); 376 | } 377 | std::this_thread::sleep_for(std::chrono::microseconds(1)); 378 | } 379 | exclusive_owner = getpid(); 380 | } 381 | bool RobustLock::try_lock() { 382 | if (!mutex_.try_lock()) { 383 | if (exclusive_owner != 0) { 384 | auto ex_proc = exclusive_owner.load(); 385 | if (proc_dead(ex_proc)) { 386 | if (exclusive_owner.compare_exchange_strong(ex_proc, 0)) { 387 | mutex_.unlock(); 388 | } 389 | } 390 | } else { 391 | prune_readers(); 392 | } 393 | if (mutex_.try_lock()) { 394 | exclusive_owner = getpid(); 395 | return true; 396 | } else { 397 | return false; 398 | } 399 | } else { 400 | exclusive_owner = getpid(); 401 | return true; 402 | } 403 | } 404 | void RobustLock::unlock() { 405 | __pid_t current_pid = getpid(); 406 | if (exclusive_owner.compare_exchange_strong(current_pid, 0)) { 407 | mutex_.unlock(); 408 | } 409 | } 410 | void RobustLock::lock_sharable() { 411 | while (!mutex_.try_lock_sharable()) { 412 | if (exclusive_owner != 0) { 413 | auto ex_proc = exclusive_owner.load(); 414 | if (proc_dead(ex_proc)) { 415 | if (exclusive_owner.compare_exchange_strong(ex_proc, 0)) { 416 | exclusive_owner = 0; 417 | mutex_.unlock(); 418 | } 419 | } 420 | } 421 | std::this_thread::sleep_for(std::chrono::microseconds(1)); 422 | } 423 | while (!shared_owners.insert(getpid())) { 424 | } 425 | } 426 | bool RobustLock::try_lock_sharable() { 427 | if (!mutex_.try_lock_sharable()) { 428 | if (exclusive_owner != 0) { 429 | auto ex_proc = exclusive_owner.load(); 430 | if (proc_dead(ex_proc)) { 431 | if (exclusive_owner.compare_exchange_strong(ex_proc, 0)) { 432 | exclusive_owner = 0; 433 | mutex_.unlock(); 434 | } 435 | } 436 | } 437 | if (mutex_.try_lock_sharable()) { 438 | while (!shared_owners.insert(getpid())) { 439 | } 440 | return true; 441 | } else { 442 | return false; 443 | } 444 | } else { 445 | while (!shared_owners.insert(getpid())) { 446 | } 447 | return true; 448 | } 449 | } 450 | void RobustLock::unlock_sharable() { 451 | if (shared_owners.remove(getpid())) { 452 | mutex_.unlock_sharable(); 453 | } 454 | } 455 | void RobustLock::reset() { mutex_.reset(); } 456 | void RobustLock::prune_readers() { 457 | for (auto &i : shared_owners.array_) { 458 | uint32_t shared_owner = i.load(); 459 | if (shared_owner == 0) continue; 460 | if (proc_dead(shared_owner)) { 461 | if (shared_owners.remove(shared_owner)) { 462 | mutex_.unlock_sharable(); 463 | } 464 | } 465 | } 466 | } 467 | } 468 | #include 469 | namespace shm::memory { 470 | #define SHMALIGN(s, a) (((s - 1) | (a - 1)) + 1) 471 | inline uint8_t *align_address(void *ptr, size_t alignment) { 472 | auto int_ptr = reinterpret_cast(ptr); 473 | auto aligned_int_ptr = SHMALIGN(int_ptr, alignment); 474 | return reinterpret_cast(aligned_int_ptr); 475 | } 476 | class Allocator { 477 | public: 478 | using handle = uint64_t; 479 | template 480 | using Scope = concurrent::ScopeGuard; 481 | Allocator(size_t offset, size_t size); 482 | uint8_t *alloc(uint32_t bytes); 483 | uint8_t *alloc(uint32_t bytes, size_t alignment); 484 | bool free(const uint8_t *ptr); 485 | void reset(); 486 | void lock_reset(); 487 | inline handle ptr_to_handle(uint8_t *p) { 488 | return p - reinterpret_cast(heap_()); 489 | } 490 | uint8_t *handle_to_ptr(handle h) { 491 | return reinterpret_cast(heap_()) + h; 492 | } 493 | size_t get_free_memory() { 494 | Scope _(&lock_); 495 | size_t free_size; 496 | size_t size = size_ / sizeof(int); 497 | if (free_index_ <= alloc_index_) { 498 | free_size = size - alloc_index_ + free_index_; 499 | } else { 500 | free_size = free_index_ - alloc_index_; 501 | } 502 | return free_size * sizeof(int); 503 | } 504 | concurrent::RobustLock lock_; 505 | private: 506 | void validate_index(uint32_t index) const; 507 | [[nodiscard]] uint32_t suggest_index(uint32_t header_index, 508 | uint32_t payload_size) const; 509 | uint32_t *__attribute__((always_inline)) heap_() { 510 | return reinterpret_cast(reinterpret_cast(this) + 511 | offset_); 512 | } 513 | uint32_t alloc_index_; 514 | volatile uint32_t free_index_; 515 | size_t offset_; 516 | size_t size_; 517 | }; 518 | Allocator::Allocator(size_t offset, size_t size) 519 | : alloc_index_(0), free_index_(0), offset_(offset), size_(size) { 520 | assert(!(size & (sizeof(int) - 1))); 521 | } 522 | void Allocator::validate_index(uint32_t index) const { 523 | assert(index < (size_ / sizeof(int))); 524 | } 525 | uint32_t Allocator::suggest_index(uint32_t header_index, 526 | uint32_t payload_size) const { 527 | validate_index(header_index); 528 | int32_t payload_index = header_index + 1; 529 | if (payload_index + payload_size - 1 >= size_ / sizeof(int)) { 530 | payload_index = 0; 531 | } 532 | validate_index(payload_index); 533 | validate_index(payload_index + payload_size - 1); 534 | return payload_index; 535 | } 536 | uint8_t *Allocator::alloc(uint32_t bytes) { return alloc(bytes, 1); } 537 | uint8_t *Allocator::alloc(uint32_t bytes, size_t alignment) { 538 | uint32_t payload_size = bytes + alignment; 539 | if (payload_size == 0) { 540 | payload_size = sizeof(int); 541 | } 542 | if (payload_size >= size_ - 2 * sizeof(int)) { 543 | return nullptr; 544 | } 545 | if (payload_size & (sizeof(int) - 1)) { 546 | payload_size &= ~(sizeof(int) - 1); 547 | payload_size += sizeof(int); 548 | } 549 | payload_size /= sizeof(int); 550 | Scope _(&lock_); 551 | const auto payload_index = suggest_index(alloc_index_, payload_size); 552 | const auto free_index_th = free_index_; 553 | uint32_t new_alloc_index = payload_index + payload_size; 554 | if (alloc_index_ < free_index_th && payload_index == 0) { 555 | return nullptr; 556 | } 557 | if (payload_index <= free_index_th && free_index_th <= new_alloc_index) { 558 | return nullptr; 559 | } 560 | if (new_alloc_index == size_ / sizeof(int)) { 561 | new_alloc_index = 0; 562 | if (new_alloc_index == free_index_th) { 563 | return nullptr; 564 | } 565 | } 566 | assert(new_alloc_index != alloc_index_); 567 | validate_index(new_alloc_index); 568 | heap_()[alloc_index_] = payload_size; 569 | alloc_index_ = new_alloc_index; 570 | auto heap_ptr = reinterpret_cast(heap_() + payload_index); 571 | return align_address(heap_ptr, alignment); 572 | } 573 | bool Allocator::free(const uint8_t *ptr) { 574 | if (ptr == nullptr) { 575 | return true; 576 | } 577 | auto *heap = reinterpret_cast(heap_()); 578 | Scope _(&lock_); 579 | assert(ptr >= heap); 580 | assert(ptr < heap + size_); 581 | validate_index(free_index_); 582 | uint32_t payload_size = heap_()[free_index_]; 583 | uint32_t payload_index = suggest_index(free_index_, payload_size); 584 | if (ptr != reinterpret_cast(heap_() + payload_index)) { 585 | return false; 586 | } 587 | uint32_t new_free_index = payload_index + payload_size; 588 | if (new_free_index == size_ / sizeof(int)) { 589 | new_free_index = 0; 590 | } 591 | free_index_ = new_free_index; 592 | return true; 593 | } 594 | void Allocator::reset() { 595 | alloc_index_ = 0; 596 | free_index_ = 0; 597 | } 598 | void Allocator::lock_reset() { lock_.reset(); } 599 | } 600 | namespace shm::memory { 601 | class DoubleAllocator { 602 | public: 603 | DoubleAllocator(size_t offset, size_t size) 604 | : req(offset, size / 2), resp(offset + size / 2, size / 2) {} 605 | Allocator req; 606 | Allocator resp; 607 | void reset() { 608 | req.reset(); 609 | resp.reset(); 610 | } 611 | void lock_reset() { 612 | req.lock_reset(); 613 | resp.lock_reset(); 614 | } 615 | }; 616 | } 617 | #include 618 | #include 619 | #include 620 | #include 621 | #include 622 | #include 623 | #include 624 | #include 625 | #include 626 | #include 627 | #include 628 | namespace shm::memory { 629 | static constexpr size_t QUEUE_SIZE = 1024; 630 | static size_t buffer_size = (1U << 28); 631 | static size_t GAP = 1024; 632 | inline uint8_t *create_memory_segment(const std::string &name, size_t size, 633 | bool *new_segment, 634 | size_t alignment = 32) { 635 | int fd; 636 | while (true) { 637 | *new_segment = true; 638 | fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644); 639 | if (fd >= 0) { 640 | fchmod(fd, 0644); 641 | } else if (errno == EEXIST) { 642 | fd = shm_open(name.c_str(), O_RDWR, 0644); 643 | if (fd < 0 && errno == ENOENT) { 644 | continue; 645 | } 646 | *new_segment = false; 647 | } else { 648 | return nullptr; 649 | } 650 | break; 651 | } 652 | if (*new_segment) { 653 | int result = ftruncate(fd, size + alignment); 654 | if (result == EINVAL) { 655 | return nullptr; 656 | } 657 | } 658 | auto *ptr = mmap(nullptr, size + alignment, PROT_READ | PROT_WRITE, 659 | MAP_SHARED, fd, 0); 660 | return align_address(ptr, alignment); 661 | } 662 | struct Memblock { 663 | void *ptr; 664 | size_t size; 665 | bool free; 666 | Memblock() : ptr(nullptr), size(0), free(true) {} 667 | Memblock(void *ptr, size_t size) : ptr(ptr), size(size), free(true) {} 668 | void no_delete() { free = false; } 669 | bool is_empty() const { return ptr == nullptr && size == 0; } 670 | }; 671 | class PIDSet { 672 | public: 673 | bool any_alive() { 674 | bool alive = false; 675 | uint32_t current_pid = getpid(); 676 | for (auto &i : pid_set.array_) { 677 | uint32_t pid = i.load(); 678 | if (pid == 0) continue; 679 | if (pid == current_pid) { 680 | alive = true; 681 | } 682 | if (proc_dead(pid)) { 683 | while (!pid_set.remove(pid)) { 684 | } 685 | } else { 686 | alive = true; 687 | } 688 | } 689 | return alive; 690 | } 691 | bool insert(uint32_t pid) { return pid_set.insert(pid); } 692 | void lock() { lck.lock(); } 693 | void unlock() { lck.unlock(); } 694 | private: 695 | concurrent::LocklessSet<32> pid_set; 696 | concurrent::RobustLock lck; 697 | }; 698 | struct Element { 699 | size_t size; 700 | bool empty; 701 | Allocator::handle address_handle; 702 | Element() : size(0), address_handle(0), empty(true) {} 703 | void reset() { 704 | size = 0; 705 | address_handle = 0; 706 | empty = true; 707 | } 708 | }; 709 | template 710 | class SharedQueue { 711 | public: 712 | std::atomic counter; 713 | std::array elements; 714 | }; 715 | template 716 | class Memory { 717 | public: 718 | explicit Memory(const std::string &name) : name_(name) { 719 | auto pid_set_size = sizeof(PIDSet); 720 | auto shared_queue_size = sizeof(SharedQueue); 721 | auto allocator_size = sizeof(AllocatorT); 722 | auto total_size = pid_set_size + shared_queue_size + allocator_size + 723 | buffer_size + 4 * GAP; 724 | bool new_segment = false; 725 | auto *base_address = 726 | create_memory_segment("/SHM_" + name, total_size, &new_segment); 727 | if (base_address == nullptr) { 728 | std::cerr << "Could not create/open shared memory segment.\n"; 729 | exit(1); 730 | } 731 | auto *pid_set_address = base_address; 732 | auto *shared_queue_address = pid_set_address + pid_set_size + GAP; 733 | auto *allocator_address = shared_queue_address + shared_queue_size + GAP; 734 | auto *buffer_address = allocator_address + allocator_size + GAP; 735 | if (new_segment) { 736 | pid_set_ = new (pid_set_address) PIDSet(); 737 | shared_queue_ = new (shared_queue_address) SharedQueue(); 738 | allocator_ = new (allocator_address) 739 | AllocatorT(buffer_address - allocator_address, buffer_size); 740 | pid_set_->insert(getpid()); 741 | init_shared_queue(); 742 | } else { 743 | pid_set_ = reinterpret_cast(pid_set_address); 744 | shared_queue_ = 745 | reinterpret_cast *>(shared_queue_address); 746 | allocator_ = reinterpret_cast(allocator_address); 747 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 748 | pid_set_->lock(); 749 | if (!pid_set_->any_alive()) { 750 | allocator_->lock_reset(); 751 | for (auto &elem : shared_queue_->elements) { 752 | elem.reset(); 753 | } 754 | shared_queue_->counter = 0; 755 | allocator_->reset(); 756 | } else { 757 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 758 | } 759 | pid_set_->unlock(); 760 | pid_set_->insert(getpid()); 761 | } 762 | } 763 | ~Memory() = default; 764 | void init_shared_queue() { 765 | shared_queue_->counter = 0; 766 | } 767 | size_t queue_size() const { return QUEUE_SIZE; } 768 | std::string name_; 769 | PIDSet *pid_set_; 770 | AllocatorT *allocator_; 771 | SharedQueue *shared_queue_; 772 | }; 773 | } 774 | #include 775 | #include 776 | #include 777 | namespace shm::rpc { 778 | struct ChannelElem { 779 | memory::Element req; 780 | memory::Element resp; 781 | concurrent::PthreadWriteLock mutex; 782 | concurrent::CondVar cond_var; 783 | ChannelElem() : req(), resp(), mutex(), cond_var() {} 784 | ChannelElem(const ChannelElem &channel_elem) { 785 | mutex = channel_elem.mutex; 786 | cond_var = channel_elem.cond_var; 787 | req = channel_elem.req; 788 | resp = channel_elem.resp; 789 | } 790 | void reset() { 791 | req.reset(); 792 | resp.reset(); 793 | mutex.reset(); 794 | cond_var.reset(); 795 | } 796 | }; 797 | class Channel { 798 | using Scope = concurrent::ScopeGuard; 800 | public: 801 | explicit Channel(const std::string &channel) 802 | : Channel(channel, std::make_shared()) {} 803 | Channel(const std::string &channel, std::shared_ptr copier) 804 | : memory_(channel) { 805 | if (copier == nullptr) { 806 | copier = std::make_shared(); 807 | } 808 | copier_ = copier; 809 | } 810 | ~Channel() = default; 811 | bool write_client(memory::Memblock memblock, uint32_t *pos) { 812 | if (memblock.size > memory_.allocator_->req.get_free_memory()) { 813 | std::cerr << "Increase buffer_size" << std::endl; 814 | return false; 815 | } 816 | *pos = counter(); 817 | auto q_pos = *pos & (queue_size() - 1); 818 | ChannelElem *elem = &(memory_.shared_queue_->elements[q_pos]); 819 | { 820 | Scope _(&elem->mutex); 821 | if (!elem->req.empty) { 822 | std::cerr << "Queue is full, try again." << std::endl; 823 | return false; 824 | } 825 | inc_counter(); 826 | elem->req.empty = false; 827 | } 828 | uint8_t *new_address = memory_.allocator_->req.alloc(memblock.size); 829 | if (new_address == nullptr) { 830 | elem->req.reset(); 831 | return false; 832 | } 833 | copier_->user_to_shm(new_address, memblock.ptr, memblock.size); 834 | { 835 | elem->req.address_handle = 836 | memory_.allocator_->req.ptr_to_handle(new_address); 837 | elem->req.size = memblock.size; 838 | } 839 | return true; 840 | } 841 | bool read_client(uint32_t pos, memory::Memblock *memblock) { 842 | uint32_t q_pos = pos & (queue_size() - 1); 843 | ChannelElem *elem = &(memory_.shared_queue_->elements[q_pos]); 844 | Scope _(&elem->mutex); 845 | while (elem->resp.empty) { 846 | elem->cond_var.wait(&elem->mutex); 847 | } 848 | auto clean_up = [this](ChannelElem *elem) { 849 | if (elem->req.size != 0) { 850 | auto address = 851 | memory_.allocator_->req.handle_to_ptr(elem->req.address_handle); 852 | memory_.allocator_->req.free(address); 853 | } 854 | elem->resp.reset(); 855 | elem->req.reset(); 856 | }; 857 | if (elem->resp.address_handle == 0) { 858 | clean_up(elem); 859 | return false; 860 | } 861 | uint8_t *address = 862 | memory_.allocator_->resp.handle_to_ptr(elem->resp.address_handle); 863 | memblock->size = elem->resp.size; 864 | memblock->ptr = copier_->alloc(memblock->size); 865 | copier_->shm_to_user(memblock->ptr, address, memblock->size); 866 | clean_up(elem); 867 | return true; 868 | } 869 | bool write_server(memory::Memblock memblock, uint32_t pos) { 870 | uint32_t q_pos = pos & (queue_size() - 1); 871 | ChannelElem *elem = &(memory_.shared_queue_->elements[q_pos]); 872 | auto signal_error = [](ChannelElem *elem) { 873 | Scope _(&elem->mutex); 874 | elem->resp.reset(); 875 | elem->resp.empty = false; 876 | elem->cond_var.signal(); 877 | }; 878 | if (memblock.size > memory_.allocator_->resp.get_free_memory()) { 879 | std::cerr << "Increase buffer_size" << std::endl; 880 | signal_error(elem); 881 | return false; 882 | } 883 | uint8_t *resp_address = memory_.allocator_->resp.alloc(memblock.size); 884 | if (resp_address == nullptr) { 885 | std::cerr << "Failed to alloc resp buffer" << std::endl; 886 | signal_error(elem); 887 | return false; 888 | } 889 | copier_->user_to_shm(resp_address, memblock.ptr, memblock.size); 890 | Scope _(&elem->mutex); 891 | elem->resp.empty = false; 892 | elem->resp.address_handle = 893 | memory_.allocator_->resp.ptr_to_handle(resp_address); 894 | elem->resp.size = memblock.size; 895 | elem->cond_var.signal(); 896 | return true; 897 | } 898 | bool read_server(uint32_t pos, memory::Memblock *memblock) { 899 | uint32_t q_pos = pos & (queue_size() - 1); 900 | ChannelElem *elem = &(memory_.shared_queue_->elements[q_pos]); 901 | Scope _(&elem->mutex); 902 | if (elem->req.empty) { 903 | return false; 904 | } 905 | uint8_t *address = 906 | memory_.allocator_->req.handle_to_ptr(elem->req.address_handle); 907 | memblock->size = elem->req.size; 908 | memblock->ptr = copier_->alloc(memblock->size); 909 | copier_->shm_to_user(memblock->ptr, address, memblock->size); 910 | return true; 911 | } 912 | inline __attribute__((always_inline)) void inc_counter() { 913 | memory_.shared_queue_->counter++; 914 | } 915 | inline __attribute__((always_inline)) uint32_t counter() const { 916 | return memory_.shared_queue_->counter.load(); 917 | } 918 | size_t queue_size() const { return memory_.queue_size(); } 919 | inline std::shared_ptr copier() const { return copier_; } 920 | private: 921 | memory::Memory memory_; 922 | std::shared_ptr copier_; 923 | }; 924 | } 925 | #include 926 | #include 927 | #include 928 | namespace shm::rpc { 929 | class Client { 930 | public: 931 | explicit Client(const std::string &channel_name); 932 | Client(const std::string &channel_name, 933 | std::shared_ptr copier); 934 | Client(const Client &) = delete; 935 | Client(Client &&); 936 | bool call(const memory::Memblock &req, memory::Memblock *resp) const; 937 | bool send(const memory::Memblock &req, uint32_t *pos) const; 938 | bool recv(uint32_t pos, memory::Memblock *resp) const; 939 | void free_resp(memory::Memblock *resp) const; 940 | private: 941 | std::string channel_name_; 942 | std::unique_ptr channel_; 943 | }; 944 | Client::Client(const std::string &channel_name) : channel_name_(channel_name) { 945 | channel_ = std::make_unique(channel_name); 946 | } 947 | Client::Client(const std::string &channel_name, 948 | std::shared_ptr copier) 949 | : channel_name_(channel_name) { 950 | channel_ = std::make_unique(channel_name, copier); 951 | } 952 | Client::Client(Client &&other) { 953 | channel_name_ = other.channel_name_; 954 | channel_ = std::move(other.channel_); 955 | } 956 | bool Client::call(const memory::Memblock &req, memory::Memblock *resp) const { 957 | uint32_t pos; 958 | bool success = send(req, &pos); 959 | if (!success) return success; 960 | return recv(pos, resp); 961 | } 962 | bool Client::send(const memory::Memblock &req, uint32_t *pos) const { 963 | return channel_->write_client(req, pos); 964 | } 965 | bool Client::recv(uint32_t pos, memory::Memblock *resp) const { 966 | return channel_->read_client(pos, resp); 967 | } 968 | void Client::free_resp(memory::Memblock *resp) const { 969 | channel_->copier()->dealloc(resp->ptr); 970 | resp->ptr = nullptr; 971 | resp->size = 0; 972 | } 973 | } 974 | #include 975 | #include 976 | #include 977 | #include 978 | #include 979 | namespace shm::rpc { 980 | using Callback = 981 | std::function; 982 | using Cleanup = std::function; 983 | inline Cleanup empty_cleanup() { 984 | return [](memory::Memblock* resp) { 985 | resp->ptr = nullptr; 986 | resp->size = 0; 987 | }; 988 | } 989 | class Server { 990 | public: 991 | Server(const std::string& channel_name, Callback cb); 992 | Server(const std::string& channel_name, Callback cb, Cleanup cln); 993 | Server(const std::string& channel_name, Callback cb, Cleanup cln, 994 | std::shared_ptr copier); 995 | Server(const Server& other) = delete; 996 | Server(Server&& other); 997 | bool serve_once(); 998 | void serve(); 999 | void stop(); 1000 | private: 1001 | bool process(uint32_t pos) const; 1002 | void cleanup_req(memory::Memblock*) const; 1003 | std::atomic_uint32_t pos_{0}; 1004 | std::atomic_bool running_{false}; 1005 | Callback callback_; 1006 | Cleanup cleanup_; 1007 | std::string channel_name_; 1008 | std::unique_ptr channel_; 1009 | }; 1010 | Server::Server(const std::string& channel_name, Callback cb) 1011 | : channel_name_(channel_name), 1012 | callback_(std::move(cb)), 1013 | cleanup_(std::move(empty_cleanup())) { 1014 | channel_ = std::make_unique(channel_name); 1015 | } 1016 | Server::Server(const std::string& channel_name, Callback cb, Cleanup cleanup) 1017 | : channel_name_(channel_name), 1018 | callback_(std::move(cb)), 1019 | cleanup_(cleanup) { 1020 | channel_ = std::make_unique(channel_name); 1021 | } 1022 | Server::Server(const std::string& channel_name, Callback cb, Cleanup cleanup, 1023 | std::shared_ptr copier) 1024 | : channel_name_(channel_name), 1025 | callback_(std::move(cb)), 1026 | cleanup_(cleanup) { 1027 | channel_ = std::make_unique(channel_name, copier); 1028 | } 1029 | Server::Server(Server&& other) { 1030 | callback_ = std::move(other.callback_); 1031 | cleanup_ = std::move(other.cleanup_); 1032 | channel_ = std::move(other.channel_); 1033 | } 1034 | void Server::cleanup_req(memory::Memblock* req) const { 1035 | if (req->is_empty()) return; 1036 | channel_->copier()->dealloc(req->ptr); 1037 | *req = memory::Memblock(nullptr, 0); 1038 | } 1039 | bool Server::process(uint32_t pos) const { 1040 | memory::Memblock req, resp; 1041 | bool success = channel_->read_server(pos, &req); 1042 | if (!success) { 1043 | cleanup_req(&req); 1044 | return success; 1045 | } 1046 | bool cbSuccess = callback_(req, &resp); 1047 | if (!cbSuccess) { 1048 | cleanup_(&resp); 1049 | resp = memory::Memblock(nullptr, 0); 1050 | cleanup_req(&req); 1051 | } 1052 | success = channel_->write_server(resp, pos); 1053 | cleanup_req(&req); 1054 | cleanup_(&resp); 1055 | return success; 1056 | } 1057 | bool Server::serve_once() { 1058 | bool success = process(pos_.fetch_add(1)); 1059 | return success; 1060 | } 1061 | void Server::serve() { 1062 | running_ = true; 1063 | while (running_.load()) { 1064 | serve_once(); 1065 | } 1066 | } 1067 | void Server::stop() { running_ = false; } 1068 | } 1069 | #include 1070 | #include 1071 | #include 1072 | #include 1073 | #include 1074 | namespace shm::pubsub { 1075 | inline uint32_t jumpahead(uint32_t counter, uint32_t queue_size) { 1076 | return counter - queue_size / 2; 1077 | } 1078 | template 1079 | struct TopicElemT { 1080 | memory::Element msg; 1081 | LockT mutex; 1082 | TopicElemT() : msg(), mutex() {} 1083 | TopicElemT(const TopicElemT &topic_elem) { 1084 | msg = topic_elem.msg; 1085 | mutex = topic_elem.mutex; 1086 | } 1087 | void reset() { 1088 | msg.reset(); 1089 | mutex.reset(); 1090 | } 1091 | }; 1092 | using LockType = concurrent::PthreadReadWriteLock; 1093 | class Topic { 1094 | using TopicElem = TopicElemT; 1095 | template 1096 | using Scope = concurrent::ScopeGuard; 1097 | public: 1098 | explicit Topic(const std::string &topic) 1099 | : Topic(topic, std::make_shared()) {} 1100 | Topic(const std::string &topic, std::shared_ptr copier) 1101 | : memory_(topic) { 1102 | if (copier == nullptr) { 1103 | copier = std::make_shared(); 1104 | } 1105 | copier_ = copier; 1106 | } 1107 | ~Topic() = default; 1108 | bool write(memory::Memblock memblock) { 1109 | if (memblock.size > memory_.allocator_->get_free_memory()) { 1110 | std::cerr << "Increase buffer_size" << std::endl; 1111 | return false; 1112 | } 1113 | uint32_t q_pos = counter() & (queue_size() - 1); 1114 | TopicElem *elem = &(memory_.shared_queue_->elements[q_pos]); 1115 | uint8_t *new_address = memory_.allocator_->alloc(memblock.size); 1116 | if (new_address == nullptr) { 1117 | return false; 1118 | } 1119 | copier_->user_to_shm(new_address, memblock.ptr, memblock.size); 1120 | uint8_t *old_address = nullptr; 1121 | { 1122 | Scope _(&elem->mutex); 1123 | if (!elem->msg.empty) { 1124 | old_address = 1125 | memory_.allocator_->handle_to_ptr(elem->msg.address_handle); 1126 | } 1127 | elem->msg.address_handle = 1128 | memory_.allocator_->ptr_to_handle(new_address); 1129 | elem->msg.size = memblock.size; 1130 | elem->msg.empty = false; 1131 | } 1132 | while (!memory_.allocator_->free(old_address)) { 1133 | std::this_thread::sleep_for(std::chrono::microseconds(100)); 1134 | } 1135 | inc_counter(); 1136 | return true; 1137 | } 1138 | bool read(memory::Memblock *memblock, std::atomic *pos) { 1139 | TopicElem *elem = 1140 | &(memory_.shared_queue_->elements[*pos & (queue_size() - 1)]); 1141 | Scope _(&elem->mutex); 1142 | #define MOVE_ELEM(_elem) \ 1143 | if (_elem->msg.empty) { \ 1144 | return false; \ 1145 | } \ 1146 | auto *dst = memory_.allocator_->handle_to_ptr(_elem->msg.address_handle); \ 1147 | memblock->size = _elem->msg.size; \ 1148 | memblock->ptr = copier_->alloc(memblock->size); \ 1149 | copier_->shm_to_user(memblock->ptr, dst, memblock->size); 1150 | if (queue_size() > counter() - *pos) { 1151 | MOVE_ELEM(elem); 1152 | return true; 1153 | } 1154 | *pos = jumpahead(counter(), queue_size()); 1155 | TopicElem *next_best_elem = 1156 | &(memory_.shared_queue_->elements[*pos & (queue_size() - 1)]); 1157 | MOVE_ELEM(next_best_elem); 1158 | return true; 1159 | #undef MOVE_ELEM 1160 | } 1161 | inline __attribute__((always_inline)) void inc_counter() { 1162 | memory_.shared_queue_->counter++; 1163 | } 1164 | inline __attribute__((always_inline)) uint32_t counter() const { 1165 | return memory_.shared_queue_->counter.load(); 1166 | } 1167 | size_t queue_size() const { return memory_.queue_size(); } 1168 | inline std::shared_ptr copier() const { return copier_; } 1169 | private: 1170 | memory::Memory memory_; 1171 | std::shared_ptr copier_; 1172 | }; 1173 | } 1174 | #include 1175 | #include 1176 | #include 1177 | #include 1178 | #include 1179 | #include 1180 | namespace shm::pubsub { 1181 | class Publisher { 1182 | public: 1183 | explicit Publisher(const std::string &topic_name); 1184 | Publisher(const std::string &topic_name, 1185 | std::shared_ptr copier); 1186 | Publisher(const Publisher &) = delete; 1187 | Publisher(Publisher &&); 1188 | bool publish(void *data, size_t size); 1189 | private: 1190 | std::string topic_name_; 1191 | std::unique_ptr topic_; 1192 | }; 1193 | Publisher::Publisher(const std::string &topic_name) : topic_name_(topic_name) { 1194 | topic_ = std::make_unique(topic_name); 1195 | } 1196 | Publisher::Publisher(const std::string &topic_name, 1197 | std::shared_ptr copier) 1198 | : topic_name_(topic_name) { 1199 | topic_ = std::make_unique(topic_name, copier); 1200 | } 1201 | Publisher::Publisher(Publisher &&other) { 1202 | topic_name_ = other.topic_name_; 1203 | topic_ = std::move(other.topic_); 1204 | } 1205 | bool Publisher::publish(void *data, size_t size) { 1206 | memory::Memblock memblock(data, size); 1207 | return topic_->write(memblock); 1208 | } 1209 | } 1210 | #include 1211 | #include 1212 | #include 1213 | #include 1214 | #include 1215 | #include 1216 | #include 1217 | #include 1218 | namespace shm::pubsub { 1219 | class Subscriber { 1220 | public: 1221 | Subscriber(const std::string &topic_name, 1222 | std::function callback); 1223 | Subscriber(const std::string &topic_name, 1224 | std::function callback, 1225 | std::shared_ptr copier); 1226 | Subscriber(const Subscriber &other) = delete; 1227 | Subscriber(Subscriber &&other); 1228 | memory::Memblock get_message(); 1229 | void spin_once(); 1230 | void spin(); 1231 | void stop(); 1232 | std::atomic counter_{0}; 1233 | private: 1234 | std::atomic_bool running_{false}; 1235 | std::function callback_; 1236 | std::string topic_name_; 1237 | std::unique_ptr topic_; 1238 | }; 1239 | Subscriber::Subscriber(const std::string &topic_name, 1240 | std::function callback) 1241 | : topic_name_(topic_name), callback_(std::move(callback)) { 1242 | topic_ = std::make_unique(topic_name_); 1243 | } 1244 | Subscriber::Subscriber(const std::string &topic_name, 1245 | std::function callback, 1246 | std::shared_ptr copier) 1247 | : topic_name_(topic_name), callback_(std::move(callback)) { 1248 | topic_ = std::make_unique(topic_name_, copier); 1249 | } 1250 | Subscriber::Subscriber(Subscriber &&other) { 1251 | callback_ = std::move(other.callback_); 1252 | topic_ = std::move(other.topic_); 1253 | } 1254 | memory::Memblock Subscriber::get_message() { 1255 | if (topic_->counter() <= counter_) { 1256 | return memory::Memblock(); 1257 | } 1258 | if (topic_->counter() - counter_ >= topic_->queue_size()) { 1259 | counter_ = jumpahead(topic_->counter(), topic_->queue_size()); 1260 | } 1261 | memory::Memblock memblock; 1262 | memblock.free = true; 1263 | if (!topic_->read(&memblock, &counter_)) { 1264 | return memory::Memblock(); 1265 | } 1266 | return memblock; 1267 | } 1268 | void Subscriber::spin_once() { 1269 | memory::Memblock memblock = get_message(); 1270 | if (memblock.is_empty()) { 1271 | return; 1272 | } 1273 | callback_(&memblock); 1274 | counter_++; 1275 | if (memblock.free) { 1276 | topic_->copier()->dealloc(memblock.ptr); 1277 | memblock.ptr = nullptr; 1278 | memblock.size = 0; 1279 | } 1280 | } 1281 | void Subscriber::spin() { 1282 | running_ = true; 1283 | while (running_.load()) { 1284 | spin_once(); 1285 | } 1286 | } 1287 | void Subscriber::stop() { running_ = false; } 1288 | } 1289 | #include 1290 | #include 1291 | #include 1292 | #include 1293 | namespace shm::memory::dragons { 1294 | #ifdef __x86_64__ 1295 | static inline void _rep_movsb(void *d, const void *s, size_t n) { 1296 | asm volatile("rep movsb" 1297 | : "=D"(d), "=S"(s), "=c"(n) 1298 | : "0"(d), "1"(s), "2"(n) 1299 | : "memory"); 1300 | } 1301 | class RepMovsbCopier : public Copier { 1302 | public: 1303 | using PtrT = uint8_t; 1304 | void *alloc(size_t size) override { return malloc(size); } 1305 | void dealloc(void *ptr) override { free(ptr); } 1306 | void shm_to_user(void *dst, void *src, size_t size) override { 1307 | _rep_movsb(dst, src, size); 1308 | } 1309 | void user_to_shm(void *dst, void *src, size_t size) override { 1310 | _rep_movsb(dst, src, size); 1311 | } 1312 | }; 1313 | #endif 1314 | #ifdef __AVX__ 1315 | static inline void _avx_cpy(void *d, const void *s, size_t n) { 1316 | auto *dVec = reinterpret_cast<__m256i *>(d); 1317 | const auto *sVec = reinterpret_cast(s); 1318 | size_t nVec = n / sizeof(__m256i); 1319 | for (; nVec > 0; nVec--, sVec++, dVec++) { 1320 | const __m256i temp = _mm256_load_si256(sVec); 1321 | _mm256_store_si256(dVec, temp); 1322 | } 1323 | } 1324 | class AvxCopier : public Copier { 1325 | public: 1326 | using PtrT = __m256i; 1327 | constexpr static size_t alignment = sizeof(__m256i); 1328 | void *alloc(size_t size) override { 1329 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 1330 | } 1331 | void dealloc(void *ptr) override { free(ptr); } 1332 | void shm_to_user(void *dst, void *src, size_t size) override { 1333 | _avx_cpy(dst, src, SHMALIGN(size, alignment)); 1334 | } 1335 | void user_to_shm(void *dst, void *src, size_t size) override { 1336 | _avx_cpy(dst, src, SHMALIGN(size, alignment)); 1337 | } 1338 | }; 1339 | #endif 1340 | #ifdef __AVX2__ 1341 | static inline void _avx_async_cpy(void *d, const void *s, size_t n) { 1342 | auto *dVec = reinterpret_cast<__m256i *>(d); 1343 | const auto *sVec = reinterpret_cast(s); 1344 | size_t nVec = n / sizeof(__m256i); 1345 | for (; nVec > 0; nVec--, sVec++, dVec++) { 1346 | const __m256i temp = _mm256_stream_load_si256(sVec); 1347 | _mm256_stream_si256(dVec, temp); 1348 | } 1349 | _mm_sfence(); 1350 | } 1351 | class AvxAsyncCopier : public Copier { 1352 | public: 1353 | using PtrT = __m256i; 1354 | constexpr static size_t alignment = sizeof(__m256i); 1355 | void *alloc(size_t size) override { 1356 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 1357 | } 1358 | void dealloc(void *ptr) override { free(ptr); } 1359 | void shm_to_user(void *dst, void *src, size_t size) override { 1360 | _avx_async_cpy(dst, src, SHMALIGN(size, alignment)); 1361 | } 1362 | void user_to_shm(void *dst, void *src, size_t size) override { 1363 | _avx_async_cpy(dst, src, SHMALIGN(size, alignment)); 1364 | } 1365 | }; 1366 | #endif 1367 | #ifdef __AVX2__ 1368 | static inline void _avx_async_pf_cpy(void *d, const void *s, size_t n) { 1369 | auto *dVec = reinterpret_cast<__m256i *>(d); 1370 | const auto *sVec = reinterpret_cast(s); 1371 | size_t nVec = n / sizeof(__m256i); 1372 | for (; nVec > 2; nVec -= 2, sVec += 2, dVec += 2) { 1373 | _mm_prefetch(sVec + 2, _MM_HINT_T0); 1374 | _mm256_stream_si256(dVec, _mm256_load_si256(sVec)); 1375 | _mm256_stream_si256(dVec + 1, _mm256_load_si256(sVec + 1)); 1376 | } 1377 | _mm256_stream_si256(dVec, _mm256_load_si256(sVec)); 1378 | _mm256_stream_si256(dVec + 1, _mm256_load_si256(sVec + 1)); 1379 | _mm_sfence(); 1380 | } 1381 | class AvxAsyncPFCopier : public Copier { 1382 | public: 1383 | using PtrT = __m256i; 1384 | constexpr static size_t alignment = sizeof(__m256i) * 2; 1385 | void *alloc(size_t size) override { 1386 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 1387 | } 1388 | void dealloc(void *ptr) override { free(ptr); } 1389 | void shm_to_user(void *dst, void *src, size_t size) override { 1390 | _avx_async_pf_cpy(dst, src, SHMALIGN(size, alignment)); 1391 | } 1392 | void user_to_shm(void *dst, void *src, size_t size) override { 1393 | _avx_async_pf_cpy(dst, src, SHMALIGN(size, alignment)); 1394 | } 1395 | }; 1396 | #endif 1397 | #ifdef __AVX__ 1398 | static inline void _avx_cpy_unroll(void *d, const void *s, size_t n) { 1399 | auto *dVec = reinterpret_cast<__m256i *>(d); 1400 | const auto *sVec = reinterpret_cast(s); 1401 | size_t nVec = n / sizeof(__m256i); 1402 | for (; nVec > 0; nVec -= 4, sVec += 4, dVec += 4) { 1403 | _mm256_store_si256(dVec, _mm256_load_si256(sVec)); 1404 | _mm256_store_si256(dVec + 1, _mm256_load_si256(sVec + 1)); 1405 | _mm256_store_si256(dVec + 2, _mm256_load_si256(sVec + 2)); 1406 | _mm256_store_si256(dVec + 3, _mm256_load_si256(sVec + 3)); 1407 | } 1408 | } 1409 | class AvxUnrollCopier : public Copier { 1410 | public: 1411 | using PtrT = __m256i; 1412 | constexpr static size_t alignment = 4 * sizeof(__m256i); 1413 | void *alloc(size_t size) override { 1414 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 1415 | } 1416 | void dealloc(void *ptr) override { free(ptr); } 1417 | void shm_to_user(void *dst, void *src, size_t size) override { 1418 | _avx_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 1419 | } 1420 | void user_to_shm(void *dst, void *src, size_t size) override { 1421 | _avx_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 1422 | } 1423 | }; 1424 | #endif 1425 | #ifdef __AVX2__ 1426 | static inline void _avx_async_cpy_unroll(void *d, const void *s, size_t n) { 1427 | auto *dVec = reinterpret_cast<__m256i *>(d); 1428 | const auto *sVec = reinterpret_cast(s); 1429 | size_t nVec = n / sizeof(__m256i); 1430 | for (; nVec > 0; nVec -= 4, sVec += 4, dVec += 4) { 1431 | _mm256_stream_si256(dVec, _mm256_stream_load_si256(sVec)); 1432 | _mm256_stream_si256(dVec + 1, _mm256_stream_load_si256(sVec + 1)); 1433 | _mm256_stream_si256(dVec + 2, _mm256_stream_load_si256(sVec + 2)); 1434 | _mm256_stream_si256(dVec + 3, _mm256_stream_load_si256(sVec + 3)); 1435 | } 1436 | _mm_sfence(); 1437 | } 1438 | class AvxAsyncUnrollCopier : public Copier { 1439 | public: 1440 | using PtrT = __m256i; 1441 | constexpr static size_t alignment = 4 * sizeof(__m256i); 1442 | void *alloc(size_t size) override { 1443 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 1444 | } 1445 | void dealloc(void *ptr) override { free(ptr); } 1446 | void shm_to_user(void *dst, void *src, size_t size) override { 1447 | _avx_async_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 1448 | } 1449 | void user_to_shm(void *dst, void *src, size_t size) override { 1450 | _avx_async_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 1451 | } 1452 | }; 1453 | #endif 1454 | #ifdef __AVX2__ 1455 | static inline void _avx_async_pf_cpy_unroll(void *d, const void *s, size_t n) { 1456 | auto *dVec = reinterpret_cast<__m256i *>(d); 1457 | const auto *sVec = reinterpret_cast(s); 1458 | size_t nVec = n / sizeof(__m256i); 1459 | for (; nVec > 4; nVec -= 4, sVec += 4, dVec += 4) { 1460 | _mm_prefetch(sVec + 4, _MM_HINT_T0); 1461 | _mm_prefetch(sVec + 6, _MM_HINT_T0); 1462 | _mm256_stream_si256(dVec, _mm256_load_si256(sVec)); 1463 | _mm256_stream_si256(dVec + 1, _mm256_load_si256(sVec + 1)); 1464 | _mm256_stream_si256(dVec + 2, _mm256_load_si256(sVec + 2)); 1465 | _mm256_stream_si256(dVec + 3, _mm256_load_si256(sVec + 3)); 1466 | } 1467 | _mm256_stream_si256(dVec, _mm256_load_si256(sVec)); 1468 | _mm256_stream_si256(dVec + 1, _mm256_load_si256(sVec + 1)); 1469 | _mm256_stream_si256(dVec + 2, _mm256_load_si256(sVec + 2)); 1470 | _mm256_stream_si256(dVec + 3, _mm256_load_si256(sVec + 3)); 1471 | _mm_sfence(); 1472 | } 1473 | class AvxAsyncPFUnrollCopier : public Copier { 1474 | public: 1475 | using PtrT = __m256i; 1476 | constexpr static size_t alignment = 4 * sizeof(__m256i); 1477 | void *alloc(size_t size) override { 1478 | return aligned_alloc(alignment, SHMALIGN(size, alignment)); 1479 | } 1480 | void dealloc(void *ptr) override { free(ptr); } 1481 | void shm_to_user(void *dst, void *src, size_t size) override { 1482 | _avx_async_pf_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 1483 | } 1484 | void user_to_shm(void *dst, void *src, size_t size) override { 1485 | _avx_async_pf_cpy_unroll(dst, src, SHMALIGN(size, alignment)); 1486 | } 1487 | }; 1488 | #endif 1489 | template 1490 | class MTCopier : public Copier { 1491 | public: 1492 | MTCopier() : base_copier() {} 1493 | void *alloc(size_t size) override { return base_copier.alloc(size); } 1494 | void dealloc(void *ptr) override { base_copier.dealloc(ptr); } 1495 | void _copy(void *d, void *s, size_t n, bool shm_to_user) { 1496 | n = SHMALIGN(n, sizeof(typename BaseCopierT::PtrT)) / 1497 | sizeof(typename BaseCopierT::PtrT); 1498 | std::vector threads; 1499 | threads.reserve(nthreads); 1500 | auto per_worker = div((int64_t)n, nthreads); 1501 | size_t next_start = 0; 1502 | for (uint32_t thread_idx = 0; thread_idx < nthreads; ++thread_idx) { 1503 | const size_t curr_start = next_start; 1504 | next_start += per_worker.quot; 1505 | if (thread_idx < per_worker.rem) { 1506 | ++next_start; 1507 | } 1508 | auto d_thread = 1509 | reinterpret_cast(d) + curr_start; 1510 | auto s_thread = 1511 | reinterpret_cast(s) + curr_start; 1512 | if (shm_to_user) { 1513 | threads.emplace_back( 1514 | &Copier::shm_to_user, &base_copier, d_thread, s_thread, 1515 | (next_start - curr_start) * sizeof(typename BaseCopierT::PtrT)); 1516 | } else { 1517 | threads.emplace_back( 1518 | &Copier::user_to_shm, &base_copier, d_thread, s_thread, 1519 | (next_start - curr_start) * sizeof(typename BaseCopierT::PtrT)); 1520 | } 1521 | } 1522 | for (auto &thread : threads) { 1523 | thread.join(); 1524 | } 1525 | threads.clear(); 1526 | } 1527 | void shm_to_user(void *dst, void *src, size_t size) override { 1528 | _copy(dst, src, size, true); 1529 | } 1530 | void user_to_shm(void *dst, void *src, size_t size) override { 1531 | _copy(dst, src, size, false); 1532 | } 1533 | private: 1534 | BaseCopierT base_copier; 1535 | }; 1536 | } 1537 | #endif -------------------------------------------------------------------------------- /scripts/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | clang-format -i --style=file include/**/*.h test/*.cpp benchmark/*.cpp 4 | -------------------------------------------------------------------------------- /scripts/install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Google benchmark 3 | git clone https://github.com/google/benchmark.git google_benchmark 4 | cd google_benchmark 5 | mkdir build 6 | cd build 7 | cmake .. -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_GTEST_TESTS=OFF 8 | make -j 4 9 | sudo make install 10 | -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cpplint \ 4 | --filter=-build/c++11,-build/include_subdir,-build/include_order \ 5 | --exclude="include/catch.hpp" \ 6 | --exclude="include/shadesmar.h" \ 7 | --exclude="release/shadesmar.h" \ 8 | --exclude="build/*" \ 9 | --exclude="cmake-build*" \ 10 | --recursive . 11 | -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | for f in build/*_test; do 5 | echo "Running test: $f" 6 | ./"$f" 7 | rm -rf /dev/shm/SHM_* 8 | done 9 | -------------------------------------------------------------------------------- /simul/README.md: -------------------------------------------------------------------------------- 1 | ## Simul 2 | 3 | Python utility that merges all the header files of Shadesmar into a single 4 | header file. 5 | 6 | Usage, from the git root: 7 | ``` 8 | $ python3 simul/simul.py 9 | ``` 10 | 11 | The combined header file will be generated as: `include/shadesmar.h`. 12 | 13 | This script is not a general purpose utility, its sole purpose is to work 14 | only on Shadesmar. 15 | -------------------------------------------------------------------------------- /simul/requirements.txt: -------------------------------------------------------------------------------- 1 | networkx 2 | -------------------------------------------------------------------------------- /simul/simul.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import sys 4 | 5 | import datetime 6 | from pathlib import Path 7 | 8 | import networkx as nx 9 | 10 | source_folder = "include/shadesmar" 11 | license_file = "LICENSE" 12 | 13 | 14 | def comment_remover(text): 15 | def replacer(match): 16 | s = match.group(0) 17 | if s.startswith('/'): 18 | return " " # note: a space and not an empty string 19 | else: 20 | return s 21 | 22 | pattern = re.compile( 23 | r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', 24 | re.DOTALL | re.MULTILINE) 25 | return re.sub(pattern, replacer, text) 26 | 27 | 28 | def fuse_lines(text_lines): 29 | text_lines = filter(lambda line: len(line.strip()) != 0, text_lines) 30 | return ''.join(text_lines) 31 | 32 | 33 | def read_file(filename): 34 | text_lines = [] 35 | with open(filename) as f: 36 | text_lines = f.readlines() 37 | 38 | # Add newline at end of file 39 | if len(text_lines) > 0: 40 | text_lines[-1] += "\n" 41 | return text_lines 42 | 43 | 44 | def remove_guards(code): 45 | # [str] 46 | guard_stack = [] 47 | guard_names = [] 48 | del_lines = [] 49 | for lno, line in enumerate(code): 50 | if line.startswith("#if"): 51 | guard_stack.append(lno) 52 | if line.startswith("#endif"): 53 | if_line = code[guard_stack[-1]] 54 | if "#ifndef INCLUDE" in if_line: 55 | del_lines.append(guard_stack[-1]) 56 | del_lines.append(lno) 57 | guard_names.append(if_line[len("#ifndef "):-1]) 58 | guard_stack.pop() 59 | 60 | for lno, line in enumerate(code): 61 | for gname in guard_names: 62 | if line.startswith("#define " + gname): 63 | del_lines.append(lno) 64 | 65 | for index in sorted(del_lines, reverse=True): 66 | if "INCLUDE" in code[index]: 67 | del code[index] 68 | 69 | return code 70 | 71 | 72 | def crawl_src_folder(folder): 73 | return [path for path in Path(folder).rglob('*.h')] 74 | 75 | 76 | def get_include_token(full_path): 77 | return str(Path(*full_path.parts[1:])) 78 | 79 | 80 | def init_graph(source_files): 81 | G = nx.DiGraph() 82 | for source_file in source_files: 83 | G.add_node(get_include_token(source_file)) 84 | return G 85 | 86 | 87 | def get_filename_text_dict(source_files): 88 | ft_dict = {} 89 | for source_file in source_files: 90 | code_with_guards = read_file(source_file) 91 | ft_dict[get_include_token(source_file)] = remove_guards(code_with_guards) 92 | return ft_dict 93 | 94 | 95 | def process_includes(source_code): 96 | shm_inc = [] 97 | other_inc = [] 98 | good_source_code = [] 99 | for line in source_code: 100 | if "#include" in line: 101 | mod = line[len("#include"):].strip()[1:-1] 102 | if "shadesmar" in mod: 103 | shm_inc.append(mod) 104 | continue 105 | else: 106 | other_inc.append(mod) 107 | good_source_code.append(line) 108 | 109 | return shm_inc, other_inc, good_source_code 110 | 111 | 112 | def add_deps_and_get_incs(g, ft_dict): 113 | global_inc = [] 114 | for filename, code in ft_dict.items(): 115 | shm_inc, other_inc, good_source_code = process_includes(code) 116 | global_inc += other_inc 117 | 118 | for s in shm_inc: 119 | g.add_edge(s, filename) 120 | 121 | ft_dict[filename] = good_source_code 122 | 123 | return sorted(list(set(global_inc))) 124 | 125 | 126 | def topo_sort(g): 127 | return list(nx.topological_sort(g)) 128 | 129 | 130 | def get_single_header(order, ft_dict, incs): 131 | header_lines = [] 132 | 133 | for o in order: 134 | header_lines += ft_dict[o] 135 | 136 | include_head = ["#ifndef SHADESMAR_SINGLE_H_\n#define SHADESMAR_SINGLE_H_\n"] 137 | include_end = ["#endif\n"] 138 | return include_head + header_lines + include_end 139 | 140 | 141 | def make_pretty(code): 142 | code = comment_remover(fuse_lines(code)) 143 | lines = code.split('\n') 144 | lines = list(filter(lambda l: l.strip() != '', lines)) 145 | code = '\n'.join(lines) 146 | return code 147 | 148 | 149 | def finalize_code(code): 150 | dt = str(datetime.datetime.utcnow()) 151 | warning_text = \ 152 | """/* 153 | THIS SINGLE HEADER FILE WAS AUTO-GENERATED USING `simul/simul.py`. 154 | DO NOT MAKE CHANGES HERE. 155 | 156 | GENERATION TIME: """ + dt + """ UTC 157 | */\n\n""" 158 | license_text = "/*" + ''.join(read_file(license_file)) + "*/\n\n" 159 | 160 | return license_text + warning_text + code 161 | 162 | 163 | # TODO: Remove per-file header guards 164 | if __name__ == '__main__': 165 | src_paths = crawl_src_folder(source_folder) 166 | ft_dict = get_filename_text_dict(src_paths) 167 | g = init_graph(src_paths) 168 | inc = add_deps_and_get_incs(g, ft_dict) 169 | order = topo_sort(g) 170 | pure_code = get_single_header(order, ft_dict, inc) 171 | clean_code = make_pretty(pure_code) 172 | final_code = finalize_code(clean_code) 173 | 174 | with open("include/shadesmar.h", "w") as f: 175 | f.write(final_code) 176 | -------------------------------------------------------------------------------- /test/allocator_test.cpp: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifdef SINGLE_HEADER 31 | #include "shadesmar.h" 32 | #else 33 | #include "shadesmar/memory/allocator.h" 34 | #endif 35 | 36 | #define CATCH_CONFIG_MAIN 37 | #include "catch.hpp" 38 | 39 | shm::memory::Allocator *new_alloc(size_t size) { 40 | auto *memory = malloc(size + sizeof(shm::memory::Allocator)); 41 | auto *alloc = new (memory) 42 | shm::memory::Allocator(sizeof(shm::memory::Allocator), size); 43 | return alloc; 44 | } 45 | 46 | TEST_CASE("basic") { 47 | auto *alloc = new_alloc(1024 * 1024); 48 | 49 | auto *x = alloc->alloc(100); 50 | auto *y = alloc->alloc(250); 51 | auto *z = alloc->alloc(1000); 52 | 53 | auto xh = alloc->ptr_to_handle(x); 54 | auto yh = alloc->ptr_to_handle(y); 55 | auto zh = alloc->ptr_to_handle(z); 56 | 57 | REQUIRE(yh - xh > 100); 58 | REQUIRE(zh - yh > 250); 59 | 60 | REQUIRE(!alloc->free(z)); 61 | REQUIRE(!alloc->free(y)); 62 | REQUIRE(alloc->free(x)); 63 | 64 | REQUIRE(!alloc->free(z)); 65 | REQUIRE(alloc->free(y)); 66 | REQUIRE(alloc->free(z)); 67 | 68 | free(alloc); 69 | } 70 | 71 | TEST_CASE("size_limit") { 72 | auto *alloc = new_alloc(100); 73 | 74 | auto *x = alloc->alloc(50); 75 | auto *y = alloc->alloc(32); 76 | auto *z = alloc->alloc(50); 77 | 78 | REQUIRE(x != nullptr); 79 | REQUIRE(y != nullptr); 80 | REQUIRE(z == nullptr); 81 | 82 | free(alloc); 83 | } 84 | 85 | TEST_CASE("perfect_wrap_around") { 86 | auto *alloc = new_alloc(100); 87 | 88 | auto *x = alloc->alloc(50); 89 | auto *y = alloc->alloc(32); 90 | auto *z = alloc->alloc(50); 91 | 92 | REQUIRE(x != nullptr); 93 | REQUIRE(y != nullptr); 94 | REQUIRE(z == nullptr); 95 | 96 | REQUIRE(alloc->free(x)); 97 | REQUIRE(alloc->free(y)); 98 | 99 | z = alloc->alloc(50); 100 | REQUIRE(z != nullptr); 101 | 102 | free(alloc); 103 | } 104 | 105 | TEST_CASE("wrap_around") { 106 | auto *alloc = new_alloc(100); 107 | 108 | auto *x = alloc->alloc(50); 109 | auto *y = alloc->alloc(32); 110 | 111 | REQUIRE(x != nullptr); 112 | REQUIRE(y != nullptr); 113 | 114 | REQUIRE(alloc->free(x)); 115 | 116 | auto *z = alloc->alloc(40); 117 | REQUIRE(z != nullptr); 118 | 119 | REQUIRE(alloc->free(y)); 120 | REQUIRE(alloc->free(z)); 121 | 122 | free(alloc); 123 | } 124 | 125 | TEST_CASE("cyclic") { 126 | auto *alloc = new_alloc(256); 127 | 128 | auto *it1 = alloc->alloc(40); 129 | auto *it2 = alloc->alloc(40); 130 | 131 | REQUIRE(it1 != nullptr); 132 | REQUIRE(it2 != nullptr); 133 | 134 | int iterations = 100; 135 | while (iterations--) { 136 | auto *it3 = alloc->alloc(40); 137 | auto *it4 = alloc->alloc(40); 138 | 139 | REQUIRE(it3 != nullptr); 140 | REQUIRE(it4 != nullptr); 141 | 142 | REQUIRE(alloc->free(it1)); 143 | REQUIRE(alloc->free(it2)); 144 | 145 | it1 = it3; 146 | it2 = it4; 147 | } 148 | 149 | REQUIRE(alloc->free(it1)); 150 | REQUIRE(alloc->free(it2)); 151 | 152 | free(alloc); 153 | } 154 | 155 | TEST_CASE("multithread", "[!mayfail]") { 156 | int nthreads = 32; 157 | std::vector allocs = {10, 200, 3000}; 158 | auto *alloc = new_alloc( 159 | 2 * std::accumulate(allocs.begin(), allocs.end(), 0) * nthreads); 160 | 161 | std::vector threads; 162 | threads.reserve(nthreads); 163 | for (int t = 0; t < nthreads; ++t) { 164 | auto rand_sleep = [&t](uint32_t factor) { 165 | uint32_t seed = t; 166 | // Sleep between (0, factor) microseconds 167 | // Seeded per thread. 168 | uint32_t timeout = 169 | static_cast(rand_r(&seed) / static_cast(RAND_MAX)) * 170 | factor; 171 | std::this_thread::sleep_for(std::chrono::microseconds(timeout)); 172 | }; 173 | 174 | threads.emplace_back([&, t]() { 175 | int iters = 100; 176 | while (iters--) { 177 | std::vector ps(allocs.size()); 178 | 179 | for (int i = 0; i < allocs.size(); ++i) { 180 | ps[i] = alloc->alloc(allocs[i]); 181 | rand_sleep(10); 182 | } 183 | 184 | for (int i = 0; i < ps.size(); ++i) { 185 | REQUIRE(ps[i] != nullptr); 186 | std::memset(ps[i], ps.size() * t + i, allocs[i]); 187 | } 188 | 189 | rand_sleep(100); 190 | 191 | for (int i = 0; i < ps.size(); ++i) { 192 | for (int l = 0; l < allocs[i]; ++i) { 193 | REQUIRE(ps[i][l] == static_cast(ps.size() * t + i)); 194 | } 195 | } 196 | 197 | int max_tries = std::max(nthreads * nthreads, 100); 198 | auto timed_free_loop = [&](uint8_t *p) -> bool { 199 | for (int i = 0; i < max_tries; ++i) { 200 | if (alloc->free(p)) { 201 | return true; 202 | } 203 | rand_sleep(50); 204 | } 205 | return false; 206 | }; 207 | 208 | for (auto p : ps) { 209 | REQUIRE(timed_free_loop(p)); 210 | } 211 | } 212 | }); 213 | } 214 | for (int t = 0; t < nthreads; ++t) { 215 | threads[t].join(); 216 | } 217 | 218 | free(alloc); 219 | } 220 | -------------------------------------------------------------------------------- /test/dragons_test.cpp: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #include 25 | #include 26 | 27 | #ifdef SINGLE_HEADER 28 | #include "shadesmar.h" 29 | #else 30 | #include "shadesmar/memory/copier.h" 31 | #include "shadesmar/memory/dragons.h" 32 | #endif 33 | 34 | void mem_check(void *mem, size_t mem_size, int num) { 35 | for (size_t i = 0; i < mem_size; ++i) { 36 | char *ptr = reinterpret_cast(mem); 37 | if (ptr[i] != num) { 38 | std::cout << "Failed" << std::endl; 39 | exit(1); 40 | } 41 | } 42 | } 43 | 44 | template 45 | void run_test(size_t mem_size, int num) { 46 | mem_size += num; // mis-aligns the data 47 | std::cout << "Test for: " << typeid(CopierT).name() << std::endl; 48 | CopierT cpy; 49 | 50 | void *shm = cpy.alloc(mem_size); 51 | void *user = cpy.alloc(mem_size); 52 | std::memset(shm, num, mem_size); 53 | 54 | cpy.shm_to_user(user, shm, mem_size); 55 | mem_check(user, mem_size, num); 56 | 57 | cpy.user_to_shm(shm, user, mem_size); 58 | mem_check(shm, mem_size, num); 59 | 60 | cpy.dealloc(shm); 61 | cpy.dealloc(user); 62 | } 63 | 64 | template 65 | void test(size_t mem_size, int num, bool mt = true) { 66 | run_test(mem_size, num); 67 | if (mt) { 68 | run_test>(mem_size, num); 69 | } 70 | } 71 | 72 | int main() { 73 | for (uint32_t i = 4; false && i < 15; i += 3) { 74 | size_t mem_size = 1 << i; 75 | std::cout << "Memory size: 2 ^ " << i << std::endl; 76 | test(mem_size, i); 77 | #ifdef __x86_64__ 78 | test(mem_size, i); 79 | #endif 80 | #ifdef __AVX__ 81 | test(mem_size, i); 82 | test(mem_size, i); 83 | #endif 84 | #ifdef __AVX2__ 85 | test(mem_size, i); 86 | test(mem_size, i); 87 | test(mem_size, i); 88 | test(mem_size, i); 89 | #endif 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /test/pubsub_test.cpp: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #ifdef SINGLE_HEADER 30 | #include "shadesmar.h" 31 | #else 32 | #include "shadesmar/pubsub/publisher.h" 33 | #include "shadesmar/pubsub/subscriber.h" 34 | #endif 35 | 36 | #define CATCH_CONFIG_MAIN 37 | #include "catch.hpp" 38 | 39 | TEST_CASE("single_message") { 40 | std::string topic = "single_message"; 41 | 42 | int message = 3; 43 | shm::pubsub::Publisher pub(topic); 44 | 45 | int answer; 46 | auto callback = [&answer](shm::memory::Memblock *memblock) { 47 | answer = *(reinterpret_cast(memblock->ptr)); 48 | }; 49 | shm::pubsub::Subscriber sub(topic, callback); 50 | 51 | pub.publish(reinterpret_cast(&message), sizeof(int)); 52 | sub.spin_once(); 53 | 54 | REQUIRE(answer == message); 55 | } 56 | 57 | TEST_CASE("multiple_messages") { 58 | std::string topic = "multiple_messages"; 59 | 60 | std::vector messages = {1, 2, 3, 4, 5}; 61 | shm::pubsub::Publisher pub(topic); 62 | 63 | std::vector answers; 64 | auto callback = [&answers](shm::memory::Memblock *memblock) { 65 | int answer = *(reinterpret_cast(memblock->ptr)); 66 | answers.push_back(answer); 67 | }; 68 | shm::pubsub::Subscriber sub(topic, callback); 69 | 70 | for (int message : messages) { 71 | std::cout << "Publishing: " << message << std::endl; 72 | pub.publish(reinterpret_cast(&message), sizeof(int)); 73 | } 74 | for (int i = 0; i < messages.size(); ++i) { 75 | sub.spin_once(); 76 | } 77 | 78 | REQUIRE(answers == messages); 79 | } 80 | 81 | TEST_CASE("alternating_pub_sub") { 82 | std::string topic = "alternating_pub_sub"; 83 | 84 | int message = 3; 85 | shm::pubsub::Publisher pub(topic); 86 | 87 | int answer; 88 | auto callback = [&answer](shm::memory::Memblock *memblock) { 89 | answer = *(reinterpret_cast(memblock->ptr)); 90 | }; 91 | shm::pubsub::Subscriber sub(topic, callback); 92 | 93 | for (int i = 0; i < 10; i++) { 94 | pub.publish(reinterpret_cast(&i), sizeof(int)); 95 | sub.spin_once(); 96 | REQUIRE(answer == i); 97 | } 98 | } 99 | 100 | TEST_CASE("single_pub_multiple_sub") { 101 | std::string topic = "single_pub_multiple_sub"; 102 | 103 | std::vector messages = {1, 2, 3, 4, 5}; 104 | shm::pubsub::Publisher pub(topic); 105 | 106 | int n_subs = 3; 107 | std::vector> vec_answers; 108 | for (int i = 0; i < n_subs; ++i) { 109 | std::vector answers; 110 | vec_answers.push_back(answers); 111 | } 112 | 113 | std::vector subs; 114 | 115 | for (int i = 0; i < n_subs; i++) { 116 | auto callback = [idx = i, &vec_answers](shm::memory::Memblock *memblock) { 117 | int answer = *(reinterpret_cast(memblock->ptr)); 118 | vec_answers[idx].push_back(answer); 119 | }; 120 | shm::pubsub::Subscriber sub(topic, callback); 121 | subs.push_back(std::move(sub)); 122 | } 123 | 124 | for (int message : messages) { 125 | std::cout << "Publishing: " << message << std::endl; 126 | pub.publish(reinterpret_cast(&message), sizeof(int)); 127 | } 128 | 129 | SECTION("messages by subscribers") { 130 | for (int i = 0; i < messages.size(); ++i) { 131 | std::cout << "Subscribe message: " << i << std::endl; 132 | for (int s = 0; s < n_subs; ++s) { 133 | std::cout << "Subscriber: " << s << std::endl; 134 | subs[s].spin_once(); 135 | } 136 | } 137 | } 138 | 139 | SECTION("subscribers by messages") { 140 | for (int s = 0; s < n_subs; ++s) { 141 | std::cout << "Subscriber: " << s << std::endl; 142 | for (int i = 0; i < messages.size(); ++i) { 143 | std::cout << "Subscribe message: " << i << std::endl; 144 | subs[s].spin_once(); 145 | } 146 | } 147 | } 148 | 149 | for (int i = 0; i < n_subs; ++i) { 150 | REQUIRE(vec_answers[i] == messages); 151 | } 152 | } 153 | 154 | TEST_CASE("multiple_pub_single_sub") { 155 | std::string topic = "multiple_pub_single_sub"; 156 | 157 | int n_pubs = 3; 158 | int n_messages = 5; 159 | 160 | std::vector pubs; 161 | for (int i = 0; i < n_pubs; ++i) { 162 | shm::pubsub::Publisher pub(topic); 163 | pubs.push_back(std::move(pub)); 164 | } 165 | 166 | std::vector answers; 167 | auto callback = [&answers](shm::memory::Memblock *memblock) { 168 | int answer = *(reinterpret_cast(memblock->ptr)); 169 | answers.push_back(answer); 170 | }; 171 | 172 | SECTION("messages by publishers") { 173 | int msg = 1; 174 | for (int m = 0; m < n_messages; ++m) { 175 | for (int p = 0; p < n_pubs; ++p) { 176 | pubs[p].publish(reinterpret_cast(&msg), sizeof(int)); 177 | msg++; 178 | } 179 | } 180 | } 181 | 182 | SECTION("publishers by messages") { 183 | int msg = 1; 184 | for (int p = 0; p < n_pubs; ++p) { 185 | for (int m = 0; m < n_messages; ++m) { 186 | pubs[p].publish(reinterpret_cast(&msg), sizeof(int)); 187 | msg++; 188 | } 189 | } 190 | } 191 | 192 | shm::pubsub::Subscriber sub(topic, callback); 193 | std::vector expected; 194 | for (int i = 0; i < n_messages * n_pubs; ++i) { 195 | sub.spin_once(); 196 | expected.push_back(i + 1); 197 | } 198 | 199 | REQUIRE(expected == answers); 200 | } 201 | 202 | TEST_CASE("multiple_pub_multiple_sub") { 203 | std::string topic = "multiple_pub_multiple_sub"; 204 | 205 | int n_pubs = 3, n_subs = 3, n_messages = 5; 206 | REQUIRE(n_pubs == n_subs); 207 | 208 | std::vector pubs; 209 | for (int i = 0; i < n_pubs; ++i) { 210 | shm::pubsub::Publisher pub(topic); 211 | pubs.push_back(std::move(pub)); 212 | } 213 | 214 | std::vector messages; 215 | int msg = 1; 216 | for (int p = 0; p < n_pubs; ++p) { 217 | for (int m = 0; m < n_messages; ++m) { 218 | messages.push_back(msg); 219 | pubs[p].publish(reinterpret_cast(&msg), sizeof(int)); 220 | msg++; 221 | } 222 | } 223 | 224 | std::vector> vec_answers; 225 | for (int i = 0; i < n_subs; ++i) { 226 | std::vector answers; 227 | vec_answers.push_back(answers); 228 | } 229 | 230 | std::vector subs; 231 | 232 | for (int i = 0; i < n_subs; i++) { 233 | auto callback = [idx = i, &vec_answers](shm::memory::Memblock *memblock) { 234 | int answer = *(reinterpret_cast(memblock->ptr)); 235 | vec_answers[idx].push_back(answer); 236 | }; 237 | shm::pubsub::Subscriber sub(topic, callback); 238 | subs.push_back(std::move(sub)); 239 | } 240 | 241 | for (int s = 0; s < n_subs; ++s) { 242 | for (int i = 0; i < messages.size(); ++i) { 243 | subs[s].spin_once(); 244 | } 245 | } 246 | 247 | for (int i = 0; i < n_subs; ++i) { 248 | REQUIRE(vec_answers[i] == messages); 249 | } 250 | } 251 | 252 | TEST_CASE("spin_without_new_msg") { 253 | std::string topic = "spin_without_new_msg"; 254 | 255 | int message = 3; 256 | shm::pubsub::Publisher pub(topic); 257 | 258 | int count = 0, answer; 259 | auto callback = [&count, &answer](shm::memory::Memblock *memblock) { 260 | answer = *(reinterpret_cast(memblock->ptr)); 261 | ++count; 262 | }; 263 | shm::pubsub::Subscriber sub(topic, callback); 264 | 265 | pub.publish(reinterpret_cast(&message), sizeof(int)); 266 | sub.spin_once(); 267 | REQUIRE(answer == 3); 268 | REQUIRE(count == 1); 269 | ++message; 270 | sub.spin_once(); 271 | REQUIRE(count == 1); 272 | ++message; 273 | pub.publish(reinterpret_cast(&message), sizeof(int)); 274 | sub.spin_once(); 275 | REQUIRE(answer == 5); 276 | REQUIRE(count == 2); 277 | } 278 | 279 | TEST_CASE("sub_counter_jump") { 280 | std::string topic = "sub_counter_jump"; 281 | shm::pubsub::Publisher pub(topic); 282 | 283 | int answer; 284 | auto callback = [&answer](shm::memory::Memblock *memblock) { 285 | answer = *(reinterpret_cast(memblock->ptr)); 286 | }; 287 | shm::pubsub::Subscriber sub(topic, callback); 288 | 289 | for (int i = 0; i < shm::memory::QUEUE_SIZE; ++i) { 290 | pub.publish(reinterpret_cast(&i), sizeof(int)); 291 | } 292 | 293 | sub.spin_once(); 294 | int lookback = 295 | shm::pubsub::jumpahead(shm::memory::QUEUE_SIZE, shm::memory::QUEUE_SIZE); 296 | REQUIRE(answer == lookback); 297 | 298 | int moveahead = shm::memory::QUEUE_SIZE - lookback + 1; 299 | for (int i = 0; i < moveahead; ++i) { 300 | int message = lookback + i; 301 | pub.publish(reinterpret_cast(&message), sizeof(int)); 302 | } 303 | 304 | sub.spin_once(); 305 | REQUIRE(answer == moveahead); 306 | } 307 | 308 | TEST_CASE("sub_after_pub_dtor") { 309 | std::string topic = "sub_after_pub_dtor"; 310 | 311 | std::vector messages = {1, 2, 3, 4, 5}; 312 | std::vector answers; 313 | 314 | { 315 | shm::pubsub::Publisher pub(topic); 316 | for (int message : messages) { 317 | std::cout << "Publishing: " << message << std::endl; 318 | pub.publish(reinterpret_cast(&message), sizeof(int)); 319 | } 320 | } 321 | 322 | { 323 | auto callback = [&answers](shm::memory::Memblock *memblock) { 324 | int answer = *(reinterpret_cast(memblock->ptr)); 325 | answers.push_back(answer); 326 | }; 327 | shm::pubsub::Subscriber sub(topic, callback); 328 | 329 | for (int i = 0; i < messages.size(); ++i) { 330 | sub.spin_once(); 331 | } 332 | } 333 | 334 | REQUIRE(answers == messages); 335 | } 336 | 337 | TEST_CASE("spin_on_thread") { 338 | std::string topic = "spin_on_thread"; 339 | 340 | std::vector messages = {1, 2, 3, 4, 5}; 341 | std::vector answers; 342 | std::mutex mu; 343 | 344 | auto callback = [&answers, &mu](shm::memory::Memblock *memblock) { 345 | std::unique_lock lock(mu); 346 | int answer = *(reinterpret_cast(memblock->ptr)); 347 | answers.push_back(answer); 348 | }; 349 | 350 | shm::pubsub::Publisher pub(topic); 351 | for (int message : messages) { 352 | std::cout << "Publishing: " << message << std::endl; 353 | pub.publish(reinterpret_cast(&message), sizeof(int)); 354 | } 355 | 356 | shm::pubsub::Subscriber sub(topic, callback); 357 | std::thread th([&]() { sub.spin(); }); 358 | while (true) { 359 | std::unique_lock lock(mu); 360 | if (answers.size() == messages.size()) { 361 | break; 362 | } 363 | } 364 | 365 | sub.stop(); 366 | th.join(); 367 | 368 | REQUIRE(answers == messages); 369 | } 370 | 371 | // TODO(squadricK): Add tests 372 | // - All the multiple pub/sub tests with parallelism 373 | -------------------------------------------------------------------------------- /test/rpc_test.cpp: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Dheeraj R Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ==============================================================================*/ 23 | 24 | #ifdef SINGLE_HEADER 25 | #include "shadesmar.h" 26 | #else 27 | #include "shadesmar/memory/memory.h" 28 | #include "shadesmar/rpc/client.h" 29 | #include "shadesmar/rpc/server.h" 30 | #endif 31 | 32 | #define CATCH_CONFIG_MAIN 33 | #include "catch.hpp" 34 | 35 | void free_cleanup(shm::memory::Memblock *resp) { 36 | free(resp->ptr); 37 | resp->ptr = nullptr; 38 | resp->size = 0; 39 | } 40 | 41 | TEST_CASE("basic") { 42 | std::string channel_name = "basic"; 43 | shm::rpc::Client client(channel_name); 44 | shm::rpc::Server server( 45 | channel_name, 46 | [](const shm::memory::Memblock &req, shm::memory::Memblock *resp) { 47 | resp->ptr = malloc(1024); 48 | resp->size = 1024; 49 | return true; 50 | }, 51 | free_cleanup); 52 | shm::memory::Memblock req, resp; 53 | uint32_t pos; 54 | REQUIRE(client.send(req, &pos)); 55 | REQUIRE(server.serve_once()); 56 | REQUIRE(client.recv(pos, &resp)); 57 | REQUIRE(!resp.is_empty()); 58 | REQUIRE(resp.size == 1024); 59 | client.free_resp(&resp); 60 | REQUIRE(resp.is_empty()); 61 | } 62 | 63 | TEST_CASE("failure") { 64 | std::string channel_name = "failure"; 65 | shm::rpc::Client client(channel_name); 66 | shm::rpc::Server server( 67 | channel_name, 68 | [](const shm::memory::Memblock &req, shm::memory::Memblock *resp) { 69 | resp->ptr = malloc(1024); 70 | resp->size = 1024; 71 | return false; 72 | }, 73 | free_cleanup); 74 | shm::memory::Memblock req, resp; 75 | uint32_t pos; 76 | REQUIRE(client.send(req, &pos)); 77 | REQUIRE(server.serve_once()); 78 | REQUIRE(client.recv(pos, &resp)); 79 | REQUIRE(resp.size == 0); 80 | client.free_resp(&resp); 81 | } 82 | 83 | TEST_CASE("single_message") { 84 | char value = 127; 85 | size_t size = 10; 86 | std::string channel_name = "single_message"; 87 | shm::rpc::Client client(channel_name); 88 | shm::rpc::Server server( 89 | channel_name, 90 | [](const shm::memory::Memblock &req, shm::memory::Memblock *resp) { 91 | resp->ptr = malloc(req.size); 92 | resp->size = req.size; 93 | std::memset(resp->ptr, static_cast(req.ptr)[0], req.size); 94 | return true; 95 | }, 96 | free_cleanup); 97 | 98 | uint32_t pos; 99 | { 100 | shm::memory::Memblock req; 101 | req.ptr = malloc(size); 102 | req.size = size; 103 | std::memset(req.ptr, value, req.size); 104 | REQUIRE(client.send(req, &pos)); 105 | free(req.ptr); 106 | } 107 | REQUIRE(server.serve_once()); 108 | { 109 | shm::memory::Memblock resp; 110 | REQUIRE(client.recv(pos, &resp)); 111 | REQUIRE(resp.size == size); 112 | REQUIRE(static_cast(resp.ptr)[0] == value); 113 | client.free_resp(&resp); 114 | } 115 | } 116 | 117 | TEST_CASE("multiple_messages") { 118 | std::string channel_name = "multiple_messages"; 119 | 120 | std::vector messages = {1, 2, 3, 4, 5}; 121 | std::vector returns; 122 | std::vector expected = {2, 4, 6, 8, 10}; 123 | 124 | shm::rpc::Client client(channel_name); 125 | shm::rpc::Server server( 126 | channel_name, 127 | [](const shm::memory::Memblock &req, shm::memory::Memblock *resp) { 128 | resp->ptr = malloc(req.size); 129 | resp->size = req.size; 130 | std::memset(resp->ptr, 2 * (static_cast(req.ptr)[0]), 131 | req.size); 132 | return true; 133 | }, 134 | free_cleanup); 135 | 136 | for (auto message : messages) { 137 | uint32_t pos; 138 | shm::memory::Memblock req; 139 | req.ptr = malloc(sizeof(char)); 140 | req.size = sizeof(char); 141 | std::memset(req.ptr, message, req.size); 142 | REQUIRE(client.send(req, &pos)); 143 | REQUIRE(server.serve_once()); 144 | free(req.ptr); 145 | shm::memory::Memblock resp; 146 | REQUIRE(client.recv(pos, &resp)); 147 | REQUIRE(resp.size == sizeof(char)); 148 | returns.push_back(static_cast(resp.ptr)[0]); 149 | client.free_resp(&resp); 150 | } 151 | REQUIRE(returns == expected); 152 | } 153 | 154 | TEST_CASE("mutliple_clients") { 155 | std::string channel_name = "multiple_clients"; 156 | 157 | std::vector messages = {1, 2, 3, 4, 5}; 158 | std::vector returns; 159 | std::vector expected = {2, 4, 6, 8, 10}; 160 | 161 | shm::rpc::Client client1(channel_name); 162 | shm::rpc::Client client2(channel_name); 163 | shm::rpc::Client *client; 164 | shm::rpc::Server server( 165 | channel_name, 166 | [](const shm::memory::Memblock &req, shm::memory::Memblock *resp) { 167 | resp->ptr = malloc(req.size); 168 | resp->size = req.size; 169 | std::memset(resp->ptr, 2 * (static_cast(req.ptr)[0]), 170 | req.size); 171 | return true; 172 | }, 173 | free_cleanup); 174 | 175 | for (auto message : messages) { 176 | client = message % 2 ? &client1 : &client2; 177 | uint32_t pos; 178 | shm::memory::Memblock req; 179 | req.ptr = malloc(sizeof(char)); 180 | req.size = sizeof(char); 181 | std::memset(req.ptr, message, req.size); 182 | REQUIRE(client->send(req, &pos)); 183 | REQUIRE(server.serve_once()); 184 | free(req.ptr); 185 | shm::memory::Memblock resp; 186 | REQUIRE(client->recv(pos, &resp)); 187 | REQUIRE(resp.size == sizeof(char)); 188 | returns.push_back(static_cast(resp.ptr)[0]); 189 | client->free_resp(&resp); 190 | } 191 | REQUIRE(returns == expected); 192 | } 193 | 194 | TEST_CASE("serve_on_separate_thread") { 195 | std::string channel_name = "serve_on_separate_thread"; 196 | 197 | std::vector messages = {1, 2, 3, 4, 5}; 198 | std::vector returns; 199 | std::vector expected = {2, 4, 6, 8, 10}; 200 | 201 | shm::rpc::Server server( 202 | channel_name, 203 | [](const shm::memory::Memblock &req, shm::memory::Memblock *resp) { 204 | resp->ptr = malloc(req.size); 205 | resp->size = req.size; 206 | std::memset(resp->ptr, 2 * (static_cast(req.ptr)[0]), 207 | req.size); 208 | return true; 209 | }, 210 | free_cleanup); 211 | std::thread th([&]() { server.serve(); }); 212 | 213 | shm::rpc::Client client(channel_name); 214 | for (auto message : messages) { 215 | shm::memory::Memblock req, resp; 216 | req.ptr = malloc(sizeof(char)); 217 | req.size = sizeof(char); 218 | std::memset(req.ptr, message, req.size); 219 | REQUIRE(client.call(req, &resp)); 220 | returns.push_back(static_cast(resp.ptr)[0]); 221 | client.free_resp(&resp); 222 | } 223 | server.stop(); 224 | th.join(); 225 | REQUIRE(returns == expected); 226 | } 227 | --------------------------------------------------------------------------------