├── BUILD ├── CPPLINT.cfg ├── README.md ├── doc ├── Makefile ├── index.md └── namespace.md ├── src └── shmc │ ├── common_utils.h │ ├── global_options.h │ ├── mmap_alloc.h │ ├── posix_shm_alloc.h │ ├── shm_alloc.h │ ├── shm_array.h │ ├── shm_handle.h │ ├── shm_hash_map.h │ ├── shm_hash_table.h │ ├── shm_hash_table_m.h │ ├── shm_link_table.h │ ├── shm_queue.h │ ├── shm_sync_buf.h │ ├── svipc_shm_alloc.h │ └── version.h └── test ├── README ├── shm_alloc_test.cc ├── shm_array_test.cc ├── shm_handle_for_heap_test.cc ├── shm_handle_test.cc ├── shm_hash_map_test.cc ├── shm_hash_table_m_test.cc ├── shm_hash_table_test.cc ├── shm_link_table_test.cc ├── shm_queue_test.cc └── shm_sync_buf_test.cc /BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | cc_library( 4 | name = "shm_container", 5 | includes = ["src"], 6 | copts = [ 7 | "-g", 8 | "-O2", 9 | "-Wall", 10 | ], 11 | linkopts = [ 12 | "-lrt", 13 | ], 14 | linkstatic = 1, 15 | srcs = glob([ 16 | "src/shmc/*.cc", 17 | "src/shmc/*.h", 18 | ]), 19 | deps = [], 20 | ) 21 | 22 | cc_test( 23 | name = "test", 24 | copts = [ 25 | "-g", 26 | "-O2", 27 | "-Wall", 28 | "-fno-strict-aliasing", 29 | "-DUNIT_TEST", 30 | ], 31 | linkstatic = 1, 32 | srcs = glob(["test/*_test.cc"]), 33 | deps = [ 34 | ":shm_container", 35 | "//gtestx", 36 | ], 37 | ) 38 | -------------------------------------------------------------------------------- /CPPLINT.cfg: -------------------------------------------------------------------------------- 1 | root=src 2 | linelength=100 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SHM-Container Documentation 2 | 3 | ## Introduction 4 | 5 | Shared-memory is best known as an effective IPC method, moreover, it is a powerful design pattern in server architecture with some notable features: 6 | 7 | * process restart without memory data lost 8 | * achieve modularity with decoupled processes 9 | * keep service HA even with failure in one process 10 | * almost no performance cost for inter process communication 11 | 12 | Many popular high-volume internet services (such as QQ server) have benefit from usage of shared-memory techniques. However it is not as easy as using in-process libraries (such as STL) when working with OS shm interface, because you have to manage memory pages directly. So what this project is valuable is that it offers a collection of shared-memory containers with familiar interface for you, and also with the following features: 13 | 14 | * provide a unified interface for different underlying shm mechanism (SYSV, SYSV\_HugeTLB, POSIX, ANON, HEAP) 15 | * provide useful container such as hash-map, fifo-queue, broadcast-queue(sync-buf), etc 16 | * efficient C++11 lock-free implemenation aimed to offer extreme high performance 17 | * designed for robust and fast, sufficient unit-tests, production ready and verified 18 | * compact and clean code, only consist of C++ headers with no external dependency 19 | 20 | See document of classes in namespace *shmc* for detail. 21 | 22 | ## Limitation 23 | 24 | Currently only Linux/x86 platform is supported. Moreover, its design goal is all for server side application. 25 | 26 | ## HowToUse 27 | 28 | The library consists only C++ headers, so importing it in your project is very easy (just add -I dir). It also provides a [bazel](https://bazel.build) BUILD file so just set deps on it if you are using bazel. 29 | 30 | If you want to build unit-tests, install bazel and [gtestx](https://github.com/mikewei/gtestx) first. 31 | 32 | ## Document 33 | 34 | You can find document online here: [SHM-Container Documentation](https://mikewei.github.io/doc/shm_container) 35 | 36 | Also you can generate doc from source: 1) install cldoc 2) cd doc; make 37 | 38 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | all: doc 2 | 3 | CXXFLAGS = -I../src --std=c++11 4 | CLDOCOPT = $(addprefix --merge , $(wildcard *.md)) --type html --output . 5 | 6 | doc: 7 | cldoc generate $(CXXFLAGS) -- $(CLDOCOPT) ../src/shmc/* 8 | 9 | server: 10 | cldoc serve . 11 | 12 | clean: 13 | rm -rf *.html *.json xml 14 | 15 | .PHONY: all doc clean 16 | 17 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | SHM-Container Documentation 4 | 5 | # SHM-Container Documentation 6 | 7 | Shared-memory is best known as an effective IPC method, moreover, it is a powerful design pattern in server architecture with some notable features: 8 | 9 | * process restart without memory data lost 10 | * achieve modularity with decoupled processes 11 | * keep service HA even with failure in one process 12 | * almost no performance cost for inter process communication 13 | 14 | Many popular high-volume internet services (such as QQ server) have benefit from usage of shared-memory techniques. However it is not as easy as using in-process libraries (such as STL) when working with OS shm interface, because you have to manage memory pages directly. So what this project is valuable is that it offers a collection of shared-memory containers with familiar interface for you, and also with the following features: 15 | 16 | * provide a unified interface for different underlying shm mechanism (SYSV, SYSV\_HugeTLB, POSIX, ANON, HEAP) 17 | * provide useful container such as hash-map, fifo-queue, broadcast-queue(sync-buf), etc 18 | * efficient C++11 lock-free implemenation aimed to offer extreme high performance 19 | * designed for robust and fast, sufficient unit-tests, production ready and verified 20 | * compact and clean code, only consist of C++ headers with no external dependency 21 | 22 | See document of classes in namespace *shmc* for detail. 23 | 24 | -------------------------------------------------------------------------------- /doc/namespace.md: -------------------------------------------------------------------------------- 1 | # 2 | namespace of library 3 | 4 | All containers support following allocators: 5 | 6 | * SVIPC - SysV IPC shared memory (shmget, shmat, ...) 7 | * SVIP\_HugeTLB - SysV IPC shared memory with HugeTLB enabled (2M page) 8 | * POSIX - POSIX shared memory (shm\_open, shm\_unlink, ...) 9 | * ANON - anonymous shared memory, allocated by mmap(2) with MAP\_SHARED|MAP\_ANONYMOUS 10 | * HEAP - process private memory, allocated by mmap(2) with MAP\_PRIVATE|MAP\_ANONYMOUS 11 | 12 | All APIs are in this namespace. See Classes listed below. 13 | 14 | # 15 | internal use 16 | -------------------------------------------------------------------------------- /src/shmc/common_utils.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_COMMON_UTILS_H_ 31 | #define SHMC_COMMON_UTILS_H_ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include "shmc/global_options.h" 42 | 43 | namespace shmc { 44 | 45 | #define SHMC_ERR_RET(...) do { \ 46 | Utils::Log(kError, __VA_ARGS__); \ 47 | return false; \ 48 | } while (0) 49 | 50 | #define SHMC_NOT_COPYABLE_AND_MOVABLE(ClassName) \ 51 | ClassName(const ClassName&) = delete; \ 52 | void operator=(const ClassName&) = delete; \ 53 | ClassName(ClassName&&) = delete; \ 54 | void operator=(ClassName&&) = delete 55 | 56 | namespace impl { 57 | 58 | //- Use dummy template as it allows static member variables defined in header 59 | template 60 | class Utils { 61 | public: 62 | static void Log(LogLevel lv, const char* fmt, ...); 63 | static void SetLogHandler(LogLevel lv, 64 | std::function f); 65 | 66 | static void SetDefaultCreateFlags(int flgs); 67 | static int DefaultCreateFlags() { 68 | return create_flags; 69 | } 70 | 71 | static const char* Perror(); 72 | static bool GetPrimeArray(size_t top, size_t num, size_t array[]); 73 | static uint64_t GenMagic(const char* str); 74 | static const char* Hex(const volatile void* buf, size_t len); 75 | static size_t GetPageSize(); 76 | 77 | static size_t MajorVer(uint32_t ver) { 78 | return static_cast(ver >> 16); 79 | } 80 | static size_t MinorVer(uint32_t ver) { 81 | return static_cast(ver & 0x0000ffffU); 82 | } 83 | static uint32_t MakeVer(size_t major, size_t minor) { 84 | return static_cast((major << 16) + (minor & 0x0000ffffU)); 85 | } 86 | 87 | template 88 | static constexpr size_t Padding() { 89 | return (sizeof(T) % N ? (N - sizeof(T) % N) : 0); 90 | } 91 | template 92 | static T RoundAlign(T num) { 93 | return (num + N - 1) / N * N; 94 | } 95 | template 96 | static T RoundAlign(T num, size_t N) { 97 | return (num + N - 1) / N * N; 98 | } 99 | 100 | private: 101 | static int log_level; 102 | static std::function log_func; 103 | static int create_flags; 104 | }; 105 | 106 | template 107 | int Utils::log_level = 0; 108 | 109 | template 110 | std::function Utils::log_func; 111 | 112 | template 113 | void Utils::Log(LogLevel lv, const char* fmt, ...) { 114 | if (lv <= log_level && log_func) { 115 | char buf[4096]; 116 | va_list args; 117 | va_start(args, fmt); 118 | vsnprintf(buf, sizeof(buf), fmt, args); 119 | va_end(args); 120 | log_func(lv, buf); 121 | } 122 | } 123 | 124 | template 125 | void Utils::SetLogHandler(LogLevel lv, 126 | std::function f) { 127 | log_level = lv; 128 | log_func = f; 129 | } 130 | 131 | template 132 | int Utils::create_flags = kCreateIfNotExist; 133 | 134 | template 135 | void Utils::SetDefaultCreateFlags(int flgs) { 136 | create_flags = flgs; 137 | } 138 | 139 | template 140 | const char* Utils::Perror() { 141 | // a tricky algorithm to support both GNU and XSI version of strerror_r 142 | static thread_local char buf[128]; 143 | buf[0] = 0; 144 | const char* s = strerror_r(errno, buf, sizeof(buf)); 145 | return (s == nullptr || (int64_t)s == -1L) ? buf : s; 146 | } 147 | 148 | static inline bool IsPrime(size_t num) { 149 | uint64_t i = 0; 150 | for (i = 2; i <= num / 2; i++) { 151 | if (0 == num % i) return false; 152 | } 153 | return true; 154 | } 155 | 156 | template 157 | bool Utils::GetPrimeArray(size_t top, size_t num, size_t array[]) { 158 | uint64_t i = 0; 159 | for (; top > 0 && num > 0; top--) { 160 | if (IsPrime(top)) { 161 | array[i++] = top; 162 | num--; 163 | } 164 | } 165 | return (num == 0); 166 | } 167 | 168 | template 169 | uint64_t Utils::GenMagic(const char* str) { 170 | uint64_t magic = 0; 171 | strncpy(reinterpret_cast(&magic), str, sizeof(magic)); 172 | return magic; 173 | } 174 | 175 | template 176 | const char* Utils::Hex(const volatile void* buf, size_t len) { 177 | static const char hex_map[16] = { 178 | '0', '1', '2', '3', '4', '5', '6', '7', 179 | '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 180 | }; 181 | thread_local static char out[8192]; 182 | size_t pos = 0; 183 | size_t i; 184 | for (i = 0; i < len && pos < 8000; i++) { 185 | out[pos++] = hex_map[((const char*)buf)[i] >> 4]; 186 | out[pos++] = hex_map[((const char*)buf)[i] & 0xf]; 187 | if ((i + 1) % 16 == 0) out[pos++] = '\n'; 188 | else if ((i + 1) % 2 == 0) out[pos++] = ' '; 189 | } 190 | if (i % 16) out[pos++] = '\n'; 191 | out[pos] = 0; 192 | return out; 193 | } 194 | 195 | template 196 | size_t Utils::GetPageSize() { 197 | int64_t ret = sysconf(_SC_PAGE_SIZE); 198 | return ret < 0 ? 4096 : static_cast(ret); 199 | } 200 | 201 | } // namespace impl 202 | 203 | 204 | using Utils = impl::Utils; 205 | 206 | /* Set the library log level and log handler 207 | * @log_level - only logs with level <= @log_level will be handled 208 | * @log_func - callback to output logs 209 | * 210 | * This function must be called before any container initialized. 211 | */ 212 | inline void SetLogHandler(LogLevel log_level, 213 | std::function log_func) { 214 | Utils::SetLogHandler(log_level, std::move(log_func)); 215 | } 216 | 217 | /* Set default create-flags used in container's InitForWrite 218 | * @create_flags - bit-OR of some ShmCreateFlag 219 | * 220 | * By default kCreateIfNotExist is used when InitForWrite is running. You can 221 | * override it by calling this function. For example, if you are upgrading 222 | * the container for larger volume you can set kCreateIfExtending to allow 223 | * delete-then-recreate behavior automatically. 224 | * This function must be called before any container initialized. 225 | */ 226 | inline void SetDefaultCreateFlags(int create_flags) { 227 | Utils::SetDefaultCreateFlags(create_flags); 228 | } 229 | 230 | } // namespace shmc 231 | 232 | #endif // SHMC_COMMON_UTILS_H_ 233 | -------------------------------------------------------------------------------- /src/shmc/global_options.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_GLOBAL_OPTIONS_H_ 31 | #define SHMC_GLOBAL_OPTIONS_H_ 32 | 33 | #include 34 | 35 | namespace shmc { 36 | 37 | /* Log level definition of the library 38 | */ 39 | enum LogLevel { 40 | kError = 1, 41 | kWarning, 42 | kInfo, 43 | kDebug, 44 | }; 45 | 46 | /* Set the library log level and log handler 47 | * @log_level - only logs with level <= @log_level will be handled 48 | * @log_func - callback to output logs 49 | * 50 | * This function must be called before any container initialized. 51 | */ 52 | void SetLogHandler(LogLevel log_level, 53 | std::function log_func); 54 | 55 | /* bit-flag of deciding when to create shm 56 | */ 57 | enum ShmCreateFlag { 58 | // (all bits cleared) never create shm only attach existing one 59 | kNoCreate = 0x0, 60 | // (1st bit) create shm if not exist 61 | kCreateIfNotExist = 0x1, 62 | // (2nd bit) create new shm if bigger size requested 63 | kCreateIfExtending = 0x2, 64 | }; 65 | 66 | /* Set default create-flags used in container's InitForWrite 67 | * @create_flags - bit-OR of some ShmCreateFlag 68 | * 69 | * By default kCreateIfNotExist is used when InitForWrite is running. You can 70 | * override it by calling this function. For example, if you are upgrading 71 | * the container for larger volume you can set kCreateIfExtending to allow 72 | * delete-then-recreate behavior automatically. 73 | * This function must be called before any container initialized. 74 | */ 75 | void SetDefaultCreateFlags(int create_flags); 76 | 77 | } // namespace shmc 78 | 79 | #endif // SHMC_GLOBAL_OPTIONS_H_ 80 | -------------------------------------------------------------------------------- /src/shmc/mmap_alloc.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_MMAP_ALLOC_H_ 31 | #define SHMC_MMAP_ALLOC_H_ 32 | 33 | #include 34 | #include 35 | #include "shmc/shm_alloc.h" 36 | #include "shmc/common_utils.h" 37 | 38 | namespace shmc { 39 | 40 | namespace impl { 41 | 42 | template 43 | class MmapAlloc : public ShmAlloc { 44 | public: 45 | virtual ~MmapAlloc() {} 46 | 47 | void* Attach(const std::string& key, size_t size, int flags, 48 | size_t* mapped_size) override { 49 | if (!(flags & kCreate)) { 50 | // cannot attach existed one 51 | set_last_errno(kErrNotExist); 52 | return nullptr; 53 | } 54 | 55 | int mmap_prot = 0; 56 | if (flags & kReadOnly) { 57 | mmap_prot = PROT_READ; 58 | } else { 59 | mmap_prot = PROT_READ | PROT_WRITE; 60 | } 61 | int share_flag = (IsShared ? MAP_SHARED : MAP_PRIVATE); 62 | void* addr = mmap(nullptr, size, mmap_prot, share_flag|MAP_ANONYMOUS, 63 | -1, 0); 64 | if (addr == reinterpret_cast(-1)) { 65 | set_last_errno(conv_errno()); 66 | return nullptr; 67 | } 68 | if (mapped_size) { 69 | size_t page_size = shmc::Utils::GetPageSize(); 70 | *mapped_size = shmc::Utils::RoundAlign(size, page_size); 71 | } 72 | return addr; 73 | } 74 | 75 | bool Detach(void* addr, size_t size) override { 76 | if (munmap(addr, size) < 0) { 77 | set_last_errno(conv_errno()); 78 | return false; 79 | } 80 | return true; 81 | } 82 | 83 | bool Unlink(const std::string& key) override { 84 | // heap pages are freed when deatched 85 | return true; 86 | } 87 | size_t AlignSize() override { 88 | return 1; 89 | } 90 | 91 | private: 92 | static ShmAllocErrno conv_errno() { 93 | switch (errno) { 94 | case 0: 95 | return kErrOK; 96 | case ENOENT: 97 | return kErrNotExist; 98 | case EEXIST: 99 | return kErrAlreadyExist; 100 | case EINVAL: 101 | return kErrInvalid; 102 | case EACCES: 103 | case EPERM: 104 | return kErrPermission; 105 | default: 106 | return kErrDefault; 107 | } 108 | } 109 | }; 110 | 111 | template 112 | struct AllocTraits> { 113 | // anonymous memory is not named 114 | static constexpr bool is_named = false; 115 | }; 116 | 117 | } // namespace impl 118 | 119 | using ANON = impl::MmapAlloc; 120 | using HEAP = impl::MmapAlloc; 121 | 122 | } // namespace shmc 123 | 124 | #endif // SHMC_MMAP_ALLOC_H_ 125 | -------------------------------------------------------------------------------- /src/shmc/posix_shm_alloc.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_POSIX_SHM_ALLOC_H_ 31 | #define SHMC_POSIX_SHM_ALLOC_H_ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "shmc/shm_alloc.h" 39 | 40 | namespace shmc { 41 | 42 | namespace impl { 43 | 44 | class POSIXShmAlloc : public ShmAlloc { 45 | public: 46 | virtual ~POSIXShmAlloc() {} 47 | 48 | void* Attach(const std::string& key, size_t size, int flags, 49 | size_t* mapped_size) override { 50 | int oflag = 0; 51 | if (flags & kCreate) 52 | oflag |= O_CREAT; 53 | if (flags & kCreateExcl) 54 | oflag |= O_EXCL; 55 | if (flags & kReadOnly) 56 | oflag |= O_RDONLY; 57 | else 58 | oflag |= O_RDWR; 59 | int fd = shm_open(key.c_str(), oflag, 0640); 60 | if (fd < 0) { 61 | set_last_errno(conv_errno()); 62 | return nullptr; 63 | } 64 | struct stat file_stat; 65 | if (fstat(fd, &file_stat) < 0) { 66 | set_last_errno(conv_errno()); 67 | close(fd); 68 | return nullptr; 69 | } 70 | size_t shm_size = file_stat.st_size; 71 | if (size > shm_size) { 72 | // kCreate means we can create new shm or enlarge old one 73 | if (flags & kCreate) { 74 | if (ftruncate(fd, size) < 0) { 75 | set_last_errno(conv_errno()); 76 | close(fd); 77 | return nullptr; 78 | } 79 | shm_size = size; 80 | } else { 81 | set_last_errno(kErrBiggerSize); 82 | close(fd); 83 | return nullptr; 84 | } 85 | } 86 | int mmap_prot = PROT_READ; 87 | if (!(flags & kReadOnly)) 88 | mmap_prot |= PROT_WRITE; 89 | void* addr = mmap(nullptr, shm_size, mmap_prot, MAP_SHARED, fd, 0); 90 | if (addr == reinterpret_cast(-1)) { 91 | set_last_errno(conv_errno()); 92 | close(fd); 93 | return nullptr; 94 | } 95 | close(fd); 96 | if (mapped_size) { 97 | *mapped_size = shm_size; 98 | } 99 | return addr; 100 | } 101 | 102 | bool Detach(void* addr, size_t size) override { 103 | if (munmap(addr, size) < 0) { 104 | set_last_errno(conv_errno()); 105 | return false; 106 | } 107 | return true; 108 | } 109 | 110 | bool Unlink(const std::string& key) override { 111 | if (shm_unlink(key.c_str()) < 0) { 112 | set_last_errno(conv_errno()); 113 | return false; 114 | } 115 | return true; 116 | } 117 | size_t AlignSize() override { 118 | return 1; 119 | } 120 | 121 | private: 122 | static ShmAllocErrno conv_errno() { 123 | switch (errno) { 124 | case 0: 125 | return kErrOK; 126 | case ENOENT: 127 | return kErrNotExist; 128 | case EEXIST: 129 | return kErrAlreadyExist; 130 | case EINVAL: 131 | return kErrInvalid; 132 | case EACCES: 133 | case EPERM: 134 | return kErrPermission; 135 | default: 136 | return kErrDefault; 137 | } 138 | } 139 | }; 140 | 141 | } // namespace impl 142 | 143 | using POSIX = impl::POSIXShmAlloc; 144 | 145 | } // namespace shmc 146 | 147 | #endif // SHMC_POSIX_SHM_ALLOC_H_ 148 | -------------------------------------------------------------------------------- /src/shmc/shm_alloc.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_SHM_ALLOC_H_ 31 | #define SHMC_SHM_ALLOC_H_ 32 | 33 | #include 34 | #include "shmc/common_utils.h" 35 | 36 | namespace shmc { 37 | namespace impl { 38 | 39 | enum ShmAllocFlag { 40 | kCreate = 0x1, 41 | kCreateExcl = 0x2, 42 | kReadOnly = 0x4, 43 | }; 44 | 45 | enum ShmAllocErrno { 46 | kErrOK = 0, 47 | kErrDefault = 1, 48 | kErrNotExist = 2, 49 | kErrAlreadyExist = 3, 50 | kErrInvalid = 4, 51 | kErrBadParam = 5, 52 | kErrPermission = 6, 53 | kErrBiggerSize = 7, 54 | }; 55 | 56 | // this defines default traits, subclasses can override it by partial-spec 57 | template 58 | struct AllocTraits { 59 | // named allocator can be found by InitForRead 60 | static constexpr bool is_named = true; 61 | }; 62 | 63 | class ShmAlloc { 64 | public: 65 | virtual ~ShmAlloc() {} 66 | int last_errno() { 67 | return last_errno_; 68 | } 69 | void* Attach_ReadOnly(const std::string& key, size_t size, 70 | size_t* mapped_size = nullptr) { 71 | return Attach(key, size, kReadOnly, mapped_size); 72 | } 73 | void* Attach_ExclCreate(const std::string& key, size_t size, 74 | size_t* mapped_size = nullptr) { 75 | return Attach(key, size, kCreate|kCreateExcl, mapped_size); 76 | } 77 | void* Attach_ForceCreate(const std::string& key, size_t size, 78 | size_t* mapped_size = nullptr) { 79 | void* addr = Attach_ExclCreate(key, size, mapped_size); 80 | if (!addr && last_errno() == kErrAlreadyExist && Unlink(key)) { 81 | addr = Attach_ExclCreate(key, size, mapped_size); 82 | } 83 | return addr; 84 | } 85 | void* Attach_MayCreate(const std::string& key, size_t size, 86 | size_t* mapped_size = nullptr, 87 | bool* created = nullptr) { 88 | if (created) *created = false; 89 | void* addr = Attach(key, size, 0, mapped_size); 90 | if (!addr && last_errno() == kErrNotExist) { 91 | addr = Attach_ExclCreate(key, size, mapped_size); 92 | if (addr && created) *created = true; 93 | } 94 | return addr; 95 | } 96 | void* Attach_AutoCreate(const std::string& key, size_t size, 97 | int create_flags = kCreateIfNotExist, 98 | size_t* mapped_size = nullptr, 99 | bool* created = nullptr) { 100 | if (created) *created = false; 101 | void* addr = nullptr; 102 | if (create_flags & kCreateIfNotExist) { 103 | addr = Attach_MayCreate(key, size, mapped_size, created); 104 | } else { 105 | addr = Attach(key, size, 0, mapped_size); 106 | } 107 | if (!addr && last_errno() == kErrBiggerSize && 108 | (create_flags & kCreateIfExtending)) { 109 | // A known corner issue: when SVIPC_HugeTLB is used (2M aligned) 110 | // and if the addional size is less than 2M it does not work because 111 | // kErrBiggerSize will not be triggered 112 | addr = Attach_ForceCreate(key, size, mapped_size); 113 | if (addr && created) *created = true; 114 | } 115 | return addr; 116 | } 117 | // interfaces to be implemented by sub-class 118 | virtual void* Attach(const std::string& key, size_t size, int flags, 119 | size_t* mapped_size = nullptr) = 0; 120 | virtual bool Detach(void* addr, size_t size) = 0; 121 | virtual bool Unlink(const std::string& key) = 0; 122 | virtual size_t AlignSize() = 0; 123 | 124 | protected: 125 | void set_last_errno(ShmAllocErrno err) { 126 | last_errno_ = err; 127 | } 128 | 129 | private: 130 | ShmAllocErrno last_errno_ = kErrOK; 131 | }; 132 | 133 | } // namespace impl 134 | } // namespace shmc 135 | 136 | #endif // SHMC_SHM_ALLOC_H_ 137 | -------------------------------------------------------------------------------- /src/shmc/shm_array.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_SHM_ARRAY_H_ 31 | #define SHMC_SHM_ARRAY_H_ 32 | 33 | #include 34 | #include 35 | #include "shmc/shm_handle.h" 36 | #include "shmc/common_utils.h" 37 | 38 | namespace shmc { 39 | 40 | /* A fixed-size array container 41 | * @Node type of array node, must be StandardLayoutType 42 | * @Alloc shm allocator to use [SVIPC(default), SVIPC_HugeTLB, POSIX, ANON, HEAP] 43 | * 44 | * This container uses a pre-allocated array of fixed-size nodes in shm. 45 | 46 | * Read-Write concurrency safety should be considered by the user, such as 47 | * race condition of reading one node and writing of the same node. 48 | */ 49 | template 50 | class ShmArray { 51 | public: 52 | ShmArray() = default; 53 | 54 | /* Initializer for READ & WRITE 55 | * @shm_key key or name of the shm to attach or create 56 | * @size size of the shm to attach or create 57 | * 58 | * This initializer should be called by the container writer/owner and 59 | * it will try to attach an existing shm or created a new one if needed. 60 | * 61 | * @return true if succeed 62 | */ 63 | bool InitForWrite(const std::string& shm_key, size_t size); 64 | 65 | /* Initializer for READ-ONLY 66 | * @shm_key key or name of the shm to attach 67 | * 68 | * This initializer should be called by the container reader and 69 | * it will try to attach an existing shm with read-only mode. 70 | * 71 | * @return true if succeed 72 | */ 73 | bool InitForRead(const std::string& shm_key); 74 | 75 | /* Get const node reference by index 76 | * @index index of array 77 | * 78 | * @return const node reference 79 | */ 80 | const volatile Node& operator[](int index) const { 81 | assert(shm_.is_initialized() && index >= 0 && 82 | static_cast(index) < shm_->node_num); 83 | return shm_->nodes[index]; 84 | } 85 | 86 | /* Get node reference by index 87 | * @index index of array 88 | * 89 | * @return node reference 90 | */ 91 | volatile Node& operator[](int index) { 92 | assert(shm_.is_initialized() && index >= 0 && 93 | static_cast(index) < shm_->node_num); 94 | return shm_->nodes[index]; 95 | } 96 | 97 | /* getter 98 | * 99 | * @return total size of array 100 | */ 101 | size_t size() const { 102 | assert(shm_.is_initialized()); 103 | return shm_->node_num; 104 | } 105 | 106 | private: 107 | SHMC_NOT_COPYABLE_AND_MOVABLE(ShmArray); 108 | 109 | struct ShmHead { 110 | volatile uint64_t magic; 111 | volatile uint32_t ver; 112 | volatile uint32_t head_size; 113 | volatile uint32_t padding; 114 | volatile uint32_t node_size; 115 | volatile uint64_t node_num; 116 | volatile uint64_t create_time; 117 | volatile char reserved[64]; 118 | volatile Node nodes[0]; 119 | }; 120 | static_assert(sizeof(ShmHead) == 104, "unexpected ShmHead layout"); 121 | static_assert(alignof(Node) <= 8, "align requirement of Node can't be met"); 122 | 123 | ShmHandle shm_; 124 | }; 125 | 126 | template 127 | bool ShmArray::InitForWrite(const std::string& shm_key, 128 | size_t size) { 129 | if (shm_.is_initialized()) { 130 | SHMC_ERR_RET("ShmArray::InitForWrite: already initialized\n"); 131 | } 132 | size_t shm_size = sizeof(ShmHead) + sizeof(Node) * size; 133 | if (!shm_.InitForWrite(shm_key, shm_size, Utils::DefaultCreateFlags())) { 134 | SHMC_ERR_RET("ShmArray::InitForWrite: shm init(%s, %lu) fail\n", 135 | shm_key.c_str(), shm_size); 136 | } 137 | uint64_t now_ts = time(NULL); 138 | if (shm_.is_newly_created()) { 139 | shm_->magic = Utils::GenMagic("Array"); 140 | shm_->ver = Utils::MakeVer(1, 0); 141 | shm_->head_size = sizeof(ShmHead); 142 | shm_->node_size = sizeof(Node); 143 | shm_->node_num = size; 144 | shm_->create_time = now_ts; 145 | } else { 146 | if (shm_->magic != Utils::GenMagic("Array")) 147 | SHMC_ERR_RET("ShmArray::InitForWrite: bad magic(0x%lx)\n", shm_->magic); 148 | if (Utils::MajorVer(shm_->ver) != 1) 149 | SHMC_ERR_RET("ShmArray::InitForWrite: bad ver(0x%x)\n", shm_->ver); 150 | if (shm_->head_size != sizeof(ShmHead)) 151 | SHMC_ERR_RET("ShmArray::InitForWrite: bad head_size(%u)\n", shm_->head_size); 152 | if (shm_->node_size != sizeof(Node)) 153 | SHMC_ERR_RET("ShmArray::InitForWrite: bad node_size(%u)\n", shm_->node_size); 154 | if (shm_->node_num != size) 155 | SHMC_ERR_RET("ShmArray::InitForWrite: bad node_num(%u)\n", shm_->node_num); 156 | } 157 | return true; 158 | } 159 | 160 | template 161 | bool ShmArray::InitForRead(const std::string& shm_key) { 162 | if (shm_.is_initialized()) { 163 | SHMC_ERR_RET("ShmArray::InitForRead: already initialized\n"); 164 | } 165 | if (!shm_.InitForRead(shm_key, sizeof(ShmHead))) { 166 | SHMC_ERR_RET("ShmArray::InitForRead: shm init(%s, %lu) fail\n", 167 | shm_key.c_str(), sizeof(ShmHead)); 168 | } 169 | if (shm_->magic != Utils::GenMagic("Array")) 170 | SHMC_ERR_RET("ShmArray::InitForRead: bad magic(0x%lx)\n", shm_->magic); 171 | if (Utils::MajorVer(shm_->ver) != 1) 172 | SHMC_ERR_RET("ShmArray::InitForRead: bad ver(0x%x)\n", shm_->ver); 173 | if (shm_->head_size != sizeof(ShmHead)) 174 | SHMC_ERR_RET("ShmArray::InitForRead: bad head_size(%u)\n", shm_->head_size); 175 | if (shm_->node_size != sizeof(Node)) 176 | SHMC_ERR_RET("ShmArray::InitForRead: bad node_size(%u)\n", shm_->node_size); 177 | size_t shm_size = sizeof(ShmHead) + sizeof(Node) * shm_->node_num; 178 | if (!shm_.CheckSize(shm_size)) 179 | SHMC_ERR_RET("ShmArray::InitForRead: bad shm size(%u)\n", shm_.size()); 180 | return true; 181 | } 182 | 183 | 184 | } // namespace shmc 185 | 186 | #endif // SHMC_SHM_ARRAY_H_ 187 | -------------------------------------------------------------------------------- /src/shmc/shm_handle.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_SHM_HANDLE_H_ 31 | #define SHMC_SHM_HANDLE_H_ 32 | 33 | #include 34 | #include "shmc/shm_alloc.h" 35 | #include "shmc/svipc_shm_alloc.h" 36 | #include "shmc/posix_shm_alloc.h" 37 | #include "shmc/mmap_alloc.h" 38 | #include "shmc/common_utils.h" 39 | 40 | namespace shmc { 41 | 42 | /* A shm reference holder 43 | * @T type of the header of shm, must StandardLayoutType 44 | * @Alloc shm allocator to use [SVIPC(default), SVIPC_HugeTLB, POSIX] 45 | * 46 | * This is a helper class for shm manipulations. It offers simple interface 47 | * to attach or create shm and hold a reference to the shm and handy methods 48 | * to access the shm data. 49 | */ 50 | template 51 | class ShmHandle { 52 | public: 53 | ShmHandle() = default; 54 | 55 | /* Destructor 56 | */ 57 | ~ShmHandle() { 58 | if (ptr_) Alloc().Detach(ptr_, size_); 59 | } 60 | 61 | /* Initializer for READ & WRITE 62 | * @shm_key key or name of the shm to attach or create 63 | * @size size of the shm 64 | * @mode decides when to create new shm 65 | * 66 | * This initializer should be called by the shm writer/owner and it will 67 | * try to attach an existing shm or created a new one if needed and allowed. 68 | * 69 | * @return true if succeed 70 | */ 71 | bool InitForWrite(const std::string& key, size_t size, 72 | int create_flags = kCreateIfNotExist) { 73 | if (is_initialized()) return false; 74 | ptr_ = static_cast(Alloc().Attach_AutoCreate(key, size, create_flags, 75 | &size_, &is_newly_created_)); 76 | if (ptr_) 77 | key_ = key; 78 | else 79 | Utils::Log(kError, "ShmHandler::InitForWrite: %s\n", Utils::Perror()); 80 | return ptr_; 81 | } 82 | 83 | /* Initializer for READ-ONLY 84 | * @shm_key key or name of the shm to attach 85 | * @size min-size of the shm to attach 86 | * 87 | * This initializer should be called by the shm reader and 88 | * it will try to attach an existing shm with read-only mode. 89 | * 90 | * @return true if succeed 91 | */ 92 | bool InitForRead(const std::string& key, size_t size) { 93 | if (is_initialized()) return false; 94 | ptr_ = static_cast(Alloc().Attach_ReadOnly(key, size, &size_)); 95 | if (ptr_) 96 | key_ = key; 97 | else 98 | Utils::Log(kError, "ShmHandler::InitForRead: %s\n", Utils::Perror()); 99 | return ptr_; 100 | } 101 | 102 | /* Reset the handle 103 | * 104 | * This will detach any attached shm. 105 | */ 106 | void Reset() { 107 | key_.clear(); 108 | if (ptr_) Alloc().Detach(ptr_, size_); 109 | ptr_ = nullptr; 110 | size_ = 0; 111 | } 112 | 113 | /* getter 114 | * 115 | * @return typed pointer to the attached shm 116 | */ 117 | T* ptr() { 118 | return ptr_; 119 | } 120 | 121 | /* getter 122 | * 123 | * @return const-typed pointer to the attached shm 124 | */ 125 | const T* ptr() const { 126 | return ptr_; 127 | } 128 | 129 | /* helper operator 130 | * 131 | * @return typed pointer to the attached shm 132 | */ 133 | T* operator->() { 134 | return ptr_; 135 | } 136 | 137 | /* helper operator 138 | * 139 | * @return const-typed pointer to the attached shm 140 | */ 141 | const T* operator->() const { 142 | return ptr_; 143 | } 144 | 145 | /* getter 146 | * 147 | * @return key string of the attached shm 148 | */ 149 | std::string key() const { 150 | return key_; 151 | } 152 | 153 | /* getter 154 | * 155 | * @return actual size of the attached shm 156 | */ 157 | size_t size() const { 158 | return size_; 159 | } 160 | 161 | /* getter 162 | * 163 | * @return whether the attached shm is newly created 164 | */ 165 | bool is_newly_created() const { 166 | return is_newly_created_; 167 | } 168 | 169 | /* getter 170 | * 171 | * @return whether the handle is initialized. 172 | */ 173 | bool is_initialized() const { 174 | return ptr_; 175 | } 176 | 177 | /* Check whether shm size_ is equal to expected_size with consideration of 178 | * alignment of the allocator 179 | * 180 | * @return true if equal 181 | */ 182 | bool CheckSize(size_t expected_size) const { 183 | return Utils::RoundAlign(expected_size, Alloc().AlignSize()) == size_; 184 | } 185 | 186 | private: 187 | SHMC_NOT_COPYABLE_AND_MOVABLE(ShmHandle); 188 | 189 | std::string key_; 190 | size_t size_ = 0; 191 | bool is_newly_created_ = false; 192 | T* ptr_ = nullptr; 193 | }; 194 | 195 | } // namespace shmc 196 | 197 | #endif // SHMC_SHM_HANDLE_H_ 198 | -------------------------------------------------------------------------------- /src/shmc/shm_hash_map.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_SHM_HASH_MAP_H_ 31 | #define SHMC_SHM_HASH_MAP_H_ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "shmc/shm_handle.h" 39 | #include "shmc/common_utils.h" 40 | #include "shmc/shm_hash_table.h" 41 | #include "shmc/shm_link_table.h" 42 | 43 | #ifdef UNIT_TEST 44 | template class ShmHashMapTest; 45 | #endif 46 | 47 | namespace shmc { 48 | 49 | /* A hash-map container of key-values 50 | * @KeyType type of key, need StandardLayoutType supporting =,==,std::hash 51 | * @Alloc shm allocator to use [SVIPC(default), SVIPC_HugeTLB, POSIX, ANON, HEAP] 52 | * 53 | * HashMap stores collection of Key-Values and supports fast queries. The key 54 | * is of scalar or struct type and the value is a variable-length buffer. 55 | * The container internally uses to index keys and uses 56 | * to store values. 57 | * 58 | * Read-Write concurrency safety is offered internally in a lock-free manner, 59 | * but Write-Write is not and needs external synchronization. 60 | */ 61 | template 62 | class ShmHashMap { 63 | public: 64 | ShmHashMap() = default; 65 | 66 | /* Initializer for READ & WRITE 67 | * @shm_key key or name of the shm to attach or create 68 | * @key_num max number of keys 69 | * @val_node_size size of each node used to store values 70 | * @val_node_num total number of nodes for values storage 71 | * 72 | * This initializer should be called by the container writer/owner and 73 | * it will try to attach an existing shm or created a new one if needed. 74 | * 75 | * @return true if succeed 76 | */ 77 | bool InitForWrite(const std::string& shm_key, 78 | size_t key_num, 79 | size_t val_node_size, 80 | size_t val_node_num); 81 | 82 | /* Initializer for READ-ONLY 83 | * @shm_key key or name of the shm to attach 84 | * 85 | * This initializer should be called by the container reader and 86 | * it will try to attach an existing shm with read-only mode. 87 | * 88 | * @return true if succeed 89 | */ 90 | bool InitForRead(const std::string& shm_key); 91 | 92 | /* Find value by key 93 | * @key key to search for 94 | * @val [out] found value 95 | * 96 | * This is a read-only operation and can be called concurrently with other 97 | * process/thread writing. 98 | * 99 | * @return true if succeed 100 | */ 101 | bool Find(const KeyType& key, std::string* val) const; 102 | 103 | /* Insert key-value (only if key not exist) 104 | * @key key to insert 105 | * @val_buf pointer to buffer of the value 106 | * @val_buf_len size of the buffer 107 | * 108 | * Try to insert key-value to the container, if the key has already exist 109 | * the method fail and return false. 110 | * 111 | * @return true if succeed 112 | */ 113 | bool Insert(const KeyType& key, const void* val_buf, size_t val_buf_len) { 114 | return DoInsert(key, val_buf, val_buf_len, false); 115 | } 116 | 117 | /* Insert key-value (only if key not exist) 118 | * @key key to insert 119 | * @val value to insert 120 | * 121 | * Try to insert key-value to the container, if the key has already exist 122 | * the method fail and return false. 123 | * 124 | * @return true if succeed 125 | */ 126 | bool Insert(const KeyType& key, const std::string& val) { 127 | return Insert(key, static_cast(val.data()), val.size()); 128 | } 129 | 130 | /* Replace key-value into the container 131 | * @key key to replace 132 | * @val_buf pointer to buffer of the value 133 | * @val_buf_len size of the buffer 134 | * 135 | * Try to replace key-value into the container, if the key does not exist 136 | * the key-value will be inserted, otherwise the value will be updated. 137 | * 138 | * @return true if succeed 139 | */ 140 | bool Replace(const KeyType& key, const void* val_buf, size_t val_buf_len) { 141 | return DoInsert(key, val_buf, val_buf_len, true); 142 | } 143 | 144 | /* Replace key-value into the container 145 | * @key key to insert 146 | * @val value to insert 147 | * 148 | * Try to replace key-value into the container, if the key does not exist 149 | * the key-value will be inserted, otherwise the value will be updated. 150 | * 151 | * @return true if succeed 152 | */ 153 | bool Replace(const KeyType& key, const std::string& val) { 154 | return Replace(key, static_cast(val.data()), val.size()); 155 | } 156 | 157 | /* Erase one key-value 158 | * @key key to find and erase 159 | * 160 | * This tries to find the key-value and erase it. 161 | * 162 | * @return true if succeed 163 | */ 164 | bool Erase(const KeyType& key); 165 | 166 | /* Dump key-value storage details to log 167 | * @key key to find and dump 168 | * 169 | * This method will try to find the key and dump the structure and content 170 | * details to log handler. 171 | * 172 | * @return true if succeed 173 | */ 174 | bool Dump(const KeyType& key) const; 175 | 176 | /* Cursor type recording current travelling position 177 | */ 178 | struct TravelPos; 179 | 180 | /* Travel key-values 181 | * @pos [in|out] current position of travel 182 | * @max_travel_nodes max nodes to travel in this method call, 0 for all 183 | * @f callback to be called when visiting each key-value 184 | * 185 | * This method will scan keys from start position and find key-values and 186 | * call the callback @f for each one. If @max_travel_nodes key-values have 187 | * been travelled this method will stop and save the position to @pos. 188 | * 189 | * @return true if succeed 190 | */ 191 | bool Travel(TravelPos* pos, size_t max_travel_nodes, 192 | std::function f); 193 | 194 | /* Travel all key-values 195 | * @f callback to be called when visiting each key-value 196 | * 197 | * This method will scan all keys and find all present key-values and 198 | * call the callback @f for each one. 199 | * 200 | * @return true if succeed 201 | */ 202 | bool Travel(std::function f) { 203 | TravelPos pos; 204 | return Travel(&pos, 0, std::move(f)); 205 | } 206 | 207 | // Health infomation about the container 208 | struct HealthStat; 209 | 210 | /* Do health check for the container. 211 | * @hstat [out] the health check result. 212 | * @auto_fix whether do error fix if possible. 213 | * 214 | * This method will do a whole health check for potential data integrity 215 | * issues such as bad linked list, corrupted data, memory leak and so on. 216 | * If errors found it usually means external unexpected overflow or bug of 217 | * the library itself. If auto_fix is true it will try to fix the error by 218 | * removing bad links, freeing leaked nodes and so on. 219 | * 220 | * @return true if finished the check successfully 221 | */ 222 | bool HealthCheck(HealthStat* hstat, bool auto_fix); 223 | 224 | /* getter 225 | * 226 | * @return total number of key-values 227 | */ 228 | size_t size() const { 229 | return link_table_.link_bufs(); 230 | } 231 | 232 | /* getter 233 | * 234 | * @return percentage of free space of HashTable index 235 | */ 236 | size_t hash_table_free_percentage() const { 237 | size_t capa = hash_table_.expected_capacity(); 238 | size_t used = link_table_.link_bufs(); 239 | if (capa < used) return 0; 240 | return (capa - used) * 100 / capa; 241 | } 242 | 243 | /* getter 244 | * 245 | * @return percentage of free space of LinkTable storage 246 | */ 247 | size_t link_table_free_percentage() const { 248 | size_t capa = link_table_.total_nodes(); 249 | size_t free = link_table_.free_nodes(); 250 | return free * 100 / capa; 251 | } 252 | 253 | /* getter 254 | * 255 | * @return percentage of free space of the container 256 | */ 257 | size_t free_percentage() const { 258 | return std::min(hash_table_free_percentage(), 259 | link_table_free_percentage()); 260 | } 261 | 262 | private: 263 | bool DoInsert(const KeyType& key, 264 | const void* val_buf, 265 | size_t val_buf_len, 266 | bool replace); 267 | struct HTNode; 268 | bool DoRead(const KeyType& key, const volatile HTNode* node, 269 | std::string* val) const; 270 | bool DoRead(const KeyType& key, const volatile HTNode* node, 271 | void* val_buf, size_t* val_len) const; 272 | static constexpr size_t kConcReadTryCount = 64; 273 | #ifdef UNIT_TEST 274 | friend class ShmHashMapTest; 275 | #endif 276 | 277 | private: 278 | SHMC_NOT_COPYABLE_AND_MOVABLE(ShmHashMap); 279 | 280 | struct HTNode { 281 | // fields 282 | volatile KeyType key; 283 | char padding1_[Utils::Padding()]; 284 | volatile link_buf_t link_buf; 285 | char padding2_[Utils::Padding()]; 286 | // ops 287 | std::pair Key() const volatile { 288 | KeyType key_cp = key; // read key before link_buf 289 | return std::pair(link_buf, key_cp); 290 | } 291 | }; 292 | static_assert(alignof(KeyType) <= 8, "align requirement of KeyType can't be met"); 293 | static_assert(alignof(HTNode) == 8, "unexpected align requirement of HTNode"); 294 | 295 | ShmHashTable hash_table_; 296 | ShmLinkTable link_table_; 297 | 298 | public: 299 | struct TravelPos : public ShmHashTable::TravelPos {}; 300 | struct HealthStat { 301 | typename ShmLinkTable::HealthStat lt_stat; 302 | size_t total_key_values; 303 | size_t bad_key_values; 304 | size_t cleared_key_values; 305 | size_t leaked_values; 306 | size_t recycled_leaked_values; 307 | }; 308 | }; 309 | 310 | template 311 | bool ShmHashMap::InitForWrite(const std::string& shm_key, 312 | size_t key_num, 313 | size_t val_node_size, 314 | size_t val_node_num) { 315 | if (!hash_table_.InitForWrite(shm_key+"0", key_num)) { 316 | SHMC_ERR_RET("ShmHashMap::InitForWrite: hash_table init fail\n"); 317 | } 318 | if (!link_table_.InitForWrite(shm_key+"1", val_node_size, val_node_num)) { 319 | SHMC_ERR_RET("ShmHashMap::InitForWrite: link_table init fail\n"); 320 | } 321 | return true; 322 | } 323 | 324 | template 325 | bool ShmHashMap::InitForRead(const std::string& shm_key) { 326 | if (!hash_table_.InitForRead(shm_key+"0")) { 327 | SHMC_ERR_RET("ShmHashMap::InitForRead: hash_table init fail\n"); 328 | } 329 | if (!link_table_.InitForRead(shm_key+"1")) { 330 | SHMC_ERR_RET("ShmHashMap::InitForRead: link_table init fail\n"); 331 | } 332 | return true; 333 | } 334 | 335 | template 336 | bool ShmHashMap::DoInsert(const KeyType& key, 337 | const void* val_buf, 338 | size_t val_buf_len, 339 | bool replace) { 340 | bool is_found; 341 | volatile HTNode* key_node = hash_table_.FindOrAlloc(key, &is_found); 342 | if (!key_node) { 343 | return false; 344 | } 345 | if (is_found && !replace) { 346 | return false; 347 | } 348 | link_buf_t lb = link_table_.New(val_buf, val_buf_len); 349 | if (!lb) { 350 | return false; 351 | } 352 | if (is_found) { 353 | link_buf_t old_lb = key_node->link_buf; 354 | key_node->link_buf = lb; 355 | link_table_.Free(old_lb); 356 | } else { 357 | // must write key before link_buf. see CHECK-KEY-POINT 358 | new (const_cast(&key_node->key)) KeyType(key); // contruct key object 359 | key_node->link_buf = lb; 360 | } 361 | return true; 362 | } 363 | 364 | template 365 | bool ShmHashMap::DoRead(const KeyType& key, 366 | const volatile HTNode* node, 367 | std::string* val) const { 368 | // Concurrency safety description 369 | // HTNode state transition: 370 | // INIT -> KEY set -> LB set -> LB reset -> ... -> LB cleared -> KEY cleared -> INIT 371 | // Atomicity requirement: 372 | // LB set/reset/clear must be atomic 373 | // KEY set/cleared need not be atomic 374 | // Optimistic lock: 375 | // ~ read LB first 376 | // ~ critical code 377 | // ~ read LB second 378 | // if two read of LB are identical and valid, the ciritical code can 379 | // see consistent value of KEY and LB-CONTENT 380 | bool ret; 381 | link_buf_t lb; 382 | size_t try_count = 0; 383 | do { // retry for RW race condition 384 | if (++try_count > kConcReadTryCount) { 385 | Utils::Log(kWarning, "ShmHashMap::DoRead fail after trying %lu times\n", try_count); 386 | return false; 387 | } 388 | lb = node->link_buf; // LOAD-LB-POINT 389 | ret = link_table_.Read(lb, val); 390 | if (ret && node->key != key) { // CHECK-KEY-POINT 391 | Utils::Log(kInfo, "ShmHashMap::DoRead fail as key has changed\n"); 392 | return false; 393 | } 394 | } while (lb != node->link_buf); // check race condition since LOAD-POINT 395 | return ret; 396 | } 397 | 398 | template 399 | bool ShmHashMap::DoRead(const KeyType& key, 400 | const volatile HTNode* node, 401 | void* val_buf, size_t* val_len) const { 402 | bool ret; 403 | link_buf_t lb; 404 | size_t try_count = 0; 405 | do { // retry for RW race condition 406 | if (++try_count > kConcReadTryCount) { 407 | Utils::Log(kWarning, "ShmHashMap::DoRead fail after trying %lu times\n", try_count); 408 | return false; 409 | } 410 | lb = node->link_buf; // LOAD-LB-POINT 411 | ret = link_table_.Read(lb, val_buf, val_len); 412 | if (ret && node->key != key) { // CHECK-KEY-POINT 413 | Utils::Log(kInfo, "ShmHashMap::DoRead fail as key has changed\n"); 414 | return false; 415 | } 416 | } while (lb != node->link_buf); // check race condition since LOAD-POINT 417 | return ret; 418 | } 419 | 420 | template 421 | bool ShmHashMap::Find(const KeyType& key, 422 | std::string* val) const { 423 | const volatile HTNode* key_node = hash_table_.Find(key); 424 | if (!key_node) { 425 | Utils::Log(kDebug, "ShmHashMap::Find: key not found\n"); 426 | return false; 427 | } 428 | return DoRead(key, key_node, val); 429 | } 430 | 431 | template 432 | bool ShmHashMap::Erase(const KeyType& key) { 433 | volatile HTNode* key_node = hash_table_.Find(key); 434 | if (!key_node) { 435 | return false; 436 | } 437 | link_buf_t lb = key_node->link_buf; 438 | // clear index first 439 | key_node->link_buf.clear(); // write link_buf before key 440 | key_node->key.~KeyType(); // destruct key object 441 | // then free link_buf 442 | link_table_.Free(lb); 443 | return true; 444 | } 445 | 446 | template 447 | bool ShmHashMap::Dump(const KeyType& key) const { 448 | Utils::Log(kInfo, "===== ShmHashMap Dump =====\n" 449 | "pid:%d key:\n%s", getpid(), Utils::Hex(&key, sizeof(key))); 450 | const volatile HTNode* key_node = hash_table_.Find(key); 451 | if (!key_node) { 452 | Utils::Log(kError, "Dump error: not found\n"); 453 | return false; 454 | } 455 | link_buf_t lb = const_cast(key_node->link_buf); 456 | return link_table_.Dump(lb); 457 | } 458 | 459 | template 460 | bool ShmHashMap::Travel(TravelPos* pos, size_t max_travel_nodes, 461 | std::function f) { 462 | std::string val; 463 | return hash_table_.Travel(pos, max_travel_nodes, 464 | [this, &f, &val](volatile HTNode* node) { 465 | KeyType key = node->key; 466 | if (DoRead(key, node, &val)) { 467 | f(key, val); 468 | } 469 | }); 470 | } 471 | 472 | template 473 | bool ShmHashMap::HealthCheck(HealthStat* hstat, bool auto_fix) { 474 | if (!link_table_.HealthCheck(&hstat->lt_stat, auto_fix)) { 475 | return false; 476 | } 477 | memset(hstat, 0, sizeof(HealthStat)); 478 | std::vector bitmap(link_table_.total_nodes() + 1); 479 | if (!hash_table_.Travel([this, hstat, auto_fix, &bitmap](volatile HTNode* node) { 480 | hstat->total_key_values++; 481 | KeyType key = node->key; 482 | link_buf_t lb = node->link_buf; 483 | size_t buf_len = 0; 484 | if (DoRead(key, node, nullptr, &buf_len)) { 485 | bitmap[lb.head()] = true; 486 | } else { 487 | hstat->bad_key_values++; 488 | if (auto_fix) { 489 | Erase(key); 490 | hstat->cleared_key_values++; 491 | hstat->total_key_values--; 492 | } 493 | } 494 | })) { 495 | return false; 496 | } 497 | if (!link_table_.Travel([this, hstat, auto_fix, &bitmap](link_buf_t lb) { 498 | if (!bitmap[lb.head()]) { 499 | hstat->leaked_values++; 500 | if (auto_fix) { 501 | link_table_.Free(lb); 502 | hstat->recycled_leaked_values++; 503 | } 504 | } 505 | })) { 506 | return false; 507 | } 508 | return true; 509 | } 510 | 511 | } // namespace shmc 512 | 513 | #endif // SHMC_SHM_HASH_MAP_H_ 514 | -------------------------------------------------------------------------------- /src/shmc/shm_hash_table.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_SHM_HASH_TABLE_H_ 31 | #define SHMC_SHM_HASH_TABLE_H_ 32 | 33 | #include 34 | #include 35 | #include 36 | #include "shmc/shm_handle.h" 37 | #include "shmc/common_utils.h" 38 | 39 | namespace shmc { 40 | 41 | /* A shm implementation of hash table (faster version, default choice) 42 | * @Key type of search key, any type supporting =,==,std::hash 43 | * @Node type of node which contains both key and value information 44 | * @Alloc shm allocator to use [SVIPC(default), SVIPC_HugeTLB, POSIX, ANON, HEAP] 45 | * 46 | * This hash table implementation uses a pre-allocated array of fixed-size 47 | * nodes in shm. The array of nodes is treated as a multi-rows hash-table and 48 | * the hash colision is solved by rehashes on different rows. The number of 49 | * rows and the size of each row is well tunned to get good time and space 50 | * performance. 51 | * 52 | * Read-Write concurrency safety should be considered by the user, for example 53 | * race conditions such as key-readying by Node::Key() and key-writing of the 54 | * same node, such as value-reading and value-updating of the same node. 55 | * 56 | * This is a low-level container, consider use first. 57 | * 58 | * Param @Node can be any StandardLayoutType with a get-key method as below: 59 | * pair Node::Key() const volatile; 60 | * or 61 | * pair Node::Key(Arg arg) const volatile; 62 | * first bool element of returned pair indicates whether the node is used, 63 | * and if true the second element holds the key of the node. 64 | */ 65 | template 66 | class ShmHashTable { 67 | public: 68 | ShmHashTable() = default; 69 | 70 | /* Initializer for READ & WRITE 71 | * @shm_key key or name of the shm to attach or create 72 | * @capacity expected capacity of hash-table 73 | * 74 | * This initializer should be called by the container writer/owner and 75 | * it will try to attach an existing shm or created a new one if needed. 76 | * 77 | * @return true if succeed 78 | */ 79 | bool InitForWrite(const std::string& shm_key, size_t capacity); 80 | 81 | /* Initializer for READ-ONLY 82 | * @shm_key key or name of the shm to attach 83 | * 84 | * This initializer should be called by the container reader and 85 | * it will try to attach an existing shm with read-only mode. 86 | * 87 | * @return true if succeed 88 | */ 89 | bool InitForRead(const std::string& shm_key); 90 | 91 | /* Find node by key 92 | * @key key to find 93 | * 94 | * This const method returns pointer to const node (read-only) if found. 95 | * 96 | * @return pointer to node if found, otherwise nullptr 97 | */ 98 | const volatile Node* Find(const Key& key) const; 99 | 100 | /* Find node by key 101 | * @key key to find 102 | * 103 | * This method returns pointer to non-const node if found and user can 104 | * update the content of the node. Note that Read-Write concurrency safety 105 | * should be considered. 106 | * 107 | * @return pointer to node if found, otherwise nullptr 108 | */ 109 | volatile Node* Find(const Key& key); 110 | 111 | /* Find node by key or allocate a new node 112 | * @key key to find 113 | * @is_found [out] whether the node is found by key if non-nullptr returned 114 | * 115 | * If a node with the key is found it returns pointer to the node, otherwise 116 | * it will find an empty node to return, if no empty node can be found it 117 | * returns nullptr. If this method returns non-nullptr is_found indicates 118 | * whether the node returned is found by key. 119 | * 120 | * @return pointer to the found node or empty node or nullptr 121 | */ 122 | volatile Node* FindOrAlloc(const Key& key, bool *is_found); 123 | 124 | /* Find node by key (with custom argument) 125 | * @key key to find 126 | * @arg argument to pass to Node::Key(Arg) 127 | * 128 | * This const method returns pointer to const node (read-only) if found. 129 | * 130 | * @return pointer to node if found, otherwise nullptr 131 | */ 132 | template 133 | const volatile Node* Find(const Key& key, Arg&& arg) const; 134 | 135 | /* Find node by key (with custom argument) 136 | * @key key to find 137 | * @arg argument to pass to Node::Key(Arg) 138 | * 139 | * This method returns pointer to non-const node if found and user can 140 | * update the content of the node. Note that Read-Write concurrency safety 141 | * should be considered. 142 | * 143 | * @return pointer to node if found, otherwise nullptr 144 | */ 145 | template 146 | volatile Node* Find(const Key& key, Arg&& arg); 147 | 148 | /* Find node by key or allocate a new node (with custom argument) 149 | * @key key to find 150 | * @arg argument to pass to Node::Key(Arg) 151 | * @is_found [out] whether the node is found by key if non-nullptr returned 152 | * 153 | * If a node with the key is found it returns pointer to the node, otherwise 154 | * it will find an empty node to return, if no empty node can be found it 155 | * returns nullptr. If this method returns non-nullptr is_found indicates 156 | * whether the node returned is found by key. 157 | * 158 | * @return pointer to the found node or empty node or nullptr 159 | */ 160 | template 161 | volatile Node* FindOrAlloc(const Key& key, Arg&& arg, bool *is_found); 162 | 163 | /* Cursor type recording current travelling position 164 | */ 165 | struct TravelPos { 166 | uint32_t row; 167 | uint32_t col; 168 | TravelPos(): row(0), col(0) {} 169 | bool at_origin() { 170 | return (row == 0 && col == 0); 171 | } 172 | }; 173 | 174 | /* Travel nodes 175 | * @pos [in|out] current position of travel 176 | * @max_travel_nodes max nodes to travel in this method call, 0 for all 177 | * @f callback to be called when visiting each node 178 | * 179 | * This method will scan array of nodes and find non-empty nodes and 180 | * call the callback @f for each one. If @max_travel_nodes nodes have 181 | * been travelled this method will stop and save the position to @pos. 182 | * 183 | * @return true if succeed 184 | */ 185 | bool Travel(TravelPos* pos, size_t max_travel_nodes, 186 | std::function f); 187 | 188 | /* Travel all nodes 189 | * @f callback to be called when visiting each node 190 | * 191 | * This method will scan array of nodes and find all non-empty nodes and 192 | * call the callback @f for each one. 193 | * 194 | * @return true if succeed 195 | */ 196 | bool Travel(std::function f) { 197 | TravelPos pos; 198 | return Travel(&pos, 0, std::move(f)); 199 | } 200 | 201 | /* getter 202 | * 203 | * @return ideal capacity of the hash-table 204 | */ 205 | size_t ideal_capacity() const { 206 | return capacity_; 207 | } 208 | 209 | /* getter 210 | * 211 | * The expected actual capacity is about 90% of ideal. 212 | * 213 | * @return expected capacity of the hash-table 214 | */ 215 | size_t expected_capacity() const { 216 | return ideal_capacity() * 85 / 100; 217 | } 218 | 219 | private: 220 | void CalcAllRows(size_t first_row_size, 221 | size_t row_size_ratio, 222 | size_t* row_num); 223 | size_t GetIndex(size_t row, size_t col) const { 224 | return row_index_[row] + col; 225 | } 226 | static size_t SquareRoot(size_t n) { 227 | for (size_t r = 0; r <= n; r++) { 228 | size_t sq = r * r; 229 | if (sq == n) return r; 230 | else if (sq > n) return r - 1; 231 | } 232 | return 0; // never get here 233 | } 234 | 235 | private: 236 | SHMC_NOT_COPYABLE_AND_MOVABLE(ShmHashTable); 237 | 238 | struct ShmHead { 239 | volatile uint64_t magic; 240 | volatile uint32_t ver; 241 | volatile uint32_t head_size; 242 | volatile uint32_t node_size; 243 | volatile uint32_t node_num; 244 | volatile uint32_t first_row_size; 245 | volatile uint32_t row_size_ratio; 246 | volatile uint32_t row_num; 247 | volatile uint32_t max_row_touched; 248 | volatile uint64_t create_time; 249 | volatile char reserved[64]; 250 | volatile Node nodes[0]; 251 | }; 252 | static_assert(sizeof(ShmHead) == 112, "unexpected ShmHead layout"); 253 | static_assert(alignof(Node) <= 8, "align requirement of Node can't be met"); 254 | 255 | ShmHandle shm_; 256 | 257 | static constexpr size_t kMaxRows = 100; 258 | static constexpr size_t kRowSizeRatio = 60; 259 | size_t row_mods_[kMaxRows]; 260 | size_t row_index_[kMaxRows]; 261 | size_t capacity_ = 0; 262 | size_t node_num_ = 0; 263 | }; 264 | 265 | template 266 | void ShmHashTable::CalcAllRows(size_t first_row_size, 267 | size_t row_size_ratio, 268 | size_t* row_num) { 269 | size_t max_rows = *row_num; 270 | size_t row_base_index = 0; 271 | size_t row_mods_sum = 0; 272 | size_t row_count = 0; 273 | for (size_t row_size = first_row_size; 274 | row_size >= 2 && row_count < max_rows; 275 | row_size = row_size * row_size_ratio / 100, row_count++) { 276 | row_index_[row_count] = row_base_index; 277 | row_base_index += row_size; 278 | bool ret = Utils::GetPrimeArray(row_size, 1, &row_mods_[row_count]); 279 | assert(ret); 280 | row_mods_sum += row_mods_[row_count]; 281 | } 282 | node_num_ = row_base_index; 283 | capacity_ = row_mods_sum; 284 | *row_num = row_count; 285 | } 286 | 287 | template 288 | bool ShmHashTable::InitForWrite(const std::string& shm_key, 289 | size_t capacity) { 290 | if (shm_.is_initialized()) { 291 | SHMC_ERR_RET("ShmHashTable::InitForWrite: already initialized\n"); 292 | } 293 | if (capacity < 1 || capacity > 0xffffffffUL*80/100) { 294 | SHMC_ERR_RET("ShmHashTable::InitForWrite: bad capacity\n"); 295 | } 296 | size_t alloc_cap = (capacity < 50 ? 100 : 297 | (capacity < 1000 ? capacity*2 : 298 | (capacity < 10000 ? capacity*3/2 : 299 | capacity*5/4))); 300 | // first_row_size / (1 - kRowSizeRatio/100) = alloc_cap 301 | size_t first_row_size = alloc_cap * (100 - kRowSizeRatio) / 100; 302 | size_t row_num = kMaxRows; 303 | CalcAllRows(first_row_size, kRowSizeRatio, &row_num); 304 | // row_num = min(row_num, row_num^0.5 * 3) 305 | size_t row_num_calc = SquareRoot(row_num * 9); 306 | if (row_num_calc < row_num) { 307 | row_num = row_num_calc; 308 | CalcAllRows(first_row_size, kRowSizeRatio, &row_num); 309 | } 310 | Utils::Log(kInfo, "ShmHashTable::InitForWrite: rows=%lu nodes=%lu cap=%lu\n", 311 | row_num, node_num_, capacity_); 312 | 313 | size_t node_size = sizeof(Node); 314 | size_t shm_size = sizeof(ShmHead) + node_size * node_num_; 315 | if (!shm_.InitForWrite(shm_key, shm_size, Utils::DefaultCreateFlags())) { 316 | SHMC_ERR_RET("ShmHashTable::InitForWrite: shm init(%s, %lu) fail\n", 317 | shm_key.c_str(), shm_size); 318 | } 319 | uint64_t now_ts = time(NULL); 320 | if (shm_.is_newly_created()) { 321 | shm_->magic = Utils::GenMagic("HashTab2"); 322 | shm_->ver = Utils::MakeVer(1, 0); 323 | shm_->head_size = sizeof(ShmHead); 324 | shm_->node_size = node_size; 325 | shm_->node_num = node_num_; 326 | shm_->first_row_size = first_row_size; 327 | shm_->row_size_ratio = kRowSizeRatio; 328 | shm_->row_num = row_num; 329 | shm_->create_time = now_ts; 330 | shm_->max_row_touched = 0; 331 | } else { 332 | if (shm_->magic != Utils::GenMagic("HashTab2")) 333 | SHMC_ERR_RET("ShmHashTable::InitForWrite: bad magic(0x%lx)\n", shm_->magic); 334 | if (Utils::MajorVer(shm_->ver) != 1) 335 | SHMC_ERR_RET("ShmHashTable::InitForWrite: bad ver(0x%x)\n", shm_->ver); 336 | if (shm_->head_size != sizeof(ShmHead)) 337 | SHMC_ERR_RET("ShmHashTable::InitForWrite: bad head_size(%u)\n", shm_->head_size); 338 | if (shm_->node_size != node_size) 339 | SHMC_ERR_RET("ShmHashTable::InitForWrite: bad node_size(%u)\n", shm_->node_size); 340 | if (shm_->node_num != node_num_) 341 | SHMC_ERR_RET("ShmHashTable::InitForWrite: bad node_num(%u)\n", shm_->node_num); 342 | if (shm_->first_row_size != first_row_size) 343 | SHMC_ERR_RET("ShmHashTable::InitForWrite: bad first_row_size(%u)\n", shm_->first_row_size); 344 | if (shm_->row_size_ratio != kRowSizeRatio) 345 | SHMC_ERR_RET("ShmHashTable::InitForWrite: bad row_size_ratio(%u)\n", shm_->row_size_ratio); 346 | if (shm_->row_num != row_num) 347 | SHMC_ERR_RET("ShmHashTable::InitForWrite: bad row_num(%u)\n", shm_->row_num); 348 | } 349 | return true; 350 | } 351 | 352 | template 353 | bool ShmHashTable::InitForRead(const std::string& shm_key) { 354 | if (shm_.is_initialized()) { 355 | SHMC_ERR_RET("ShmHashTable::InitForRead: already initialized\n"); 356 | } 357 | size_t node_size = sizeof(Node); 358 | if (!shm_.InitForRead(shm_key, sizeof(ShmHead))) { 359 | SHMC_ERR_RET("ShmHashTable::InitForRead: shm init(%s, %lu) fail\n", 360 | shm_key.c_str(), sizeof(ShmHead)); 361 | } 362 | if (shm_->magic != Utils::GenMagic("HashTab2")) 363 | SHMC_ERR_RET("ShmHashTable::InitForRead: bad magic(0x%lx)\n", shm_->magic); 364 | if (Utils::MajorVer(shm_->ver) != 1) 365 | SHMC_ERR_RET("ShmHashTable::InitForRead: bad ver(0x%x)\n", shm_->ver); 366 | if (shm_->head_size != sizeof(ShmHead)) 367 | SHMC_ERR_RET("ShmHashTable::InitForRead: bad head_size(%u)\n", shm_->head_size); 368 | if (shm_->node_size != node_size) 369 | SHMC_ERR_RET("ShmHashTable::InitForRead: bad node_size(%u)\n", shm_->node_size); 370 | size_t shm_size = sizeof(ShmHead) + node_size * shm_->node_num; 371 | if (!shm_.CheckSize(shm_size)) 372 | SHMC_ERR_RET("ShmHashTable::InitForRead: bad shm size(%u)\n", shm_.size()); 373 | size_t row_num = shm_->row_num; 374 | CalcAllRows(shm_->first_row_size, shm_->row_size_ratio, &row_num); 375 | if (shm_->row_num != row_num) 376 | SHMC_ERR_RET("ShmHashTable::InitForRead: bad row_num(%u)\n", shm_->row_num); 377 | if (shm_->node_num != node_num_) 378 | SHMC_ERR_RET("ShmHashTable::InitForRead: bad node_num(%u)\n", shm_->node_num); 379 | return true; 380 | } 381 | 382 | template 383 | const volatile Node* ShmHashTable::Find(const Key& key) const { 384 | assert(shm_.is_initialized()); 385 | size_t hash_code = std::hash()(key); 386 | for (size_t r = 0; r < shm_->row_num; r++) { 387 | size_t index = GetIndex(r, hash_code % row_mods_[r]); 388 | const volatile Node* node = &shm_->nodes[index]; 389 | auto key_info = node->Key(); 390 | if (key_info.first && key == key_info.second) { 391 | return node; 392 | } 393 | } 394 | return nullptr; 395 | } 396 | 397 | template 398 | volatile Node* ShmHashTable::Find(const Key& key) { 399 | assert(shm_.is_initialized()); 400 | size_t hash_code = std::hash()(key); 401 | for (size_t r = 0; r < shm_->row_num; r++) { 402 | size_t index = GetIndex(r, hash_code % row_mods_[r]); 403 | volatile Node* node = &shm_->nodes[index]; 404 | auto key_info = node->Key(); 405 | if (key_info.first && key == key_info.second) { 406 | return node; 407 | } 408 | } 409 | return nullptr; 410 | } 411 | 412 | template 413 | volatile Node* ShmHashTable::FindOrAlloc(const Key& key, 414 | bool* is_found) { 415 | assert(shm_.is_initialized()); 416 | volatile Node* empty_node = nullptr; 417 | if (is_found) *is_found = false; 418 | size_t hash_code = std::hash()(key); 419 | for (size_t r = 0; r < shm_->row_num; r++) { 420 | size_t index = GetIndex(r, hash_code % row_mods_[r]); 421 | volatile Node* node = &shm_->nodes[index]; 422 | auto key_info = node->Key(); 423 | if (key_info.first) { // not empty 424 | if (key == key_info.second) { 425 | if (is_found) *is_found = true; 426 | return node; 427 | } 428 | } else if (!empty_node) { // emtpy 429 | empty_node = node; 430 | if (r > shm_->max_row_touched) 431 | shm_->max_row_touched = r; 432 | } 433 | } 434 | return empty_node; 435 | } 436 | 437 | template 438 | template 439 | const volatile Node* ShmHashTable::Find(const Key& key, 440 | Arg&& arg) const { 441 | assert(shm_.is_initialized()); 442 | size_t hash_code = std::hash()(key); 443 | for (size_t r = 0; r < shm_->row_num; r++) { 444 | size_t index = GetIndex(r, hash_code % row_mods_[r]); 445 | const volatile Node* node = &shm_->nodes[index]; 446 | auto key_info = node->Key(std::forward(arg)); 447 | if (key_info.first && key == key_info.second) { 448 | return node; 449 | } 450 | } 451 | return nullptr; 452 | } 453 | 454 | template 455 | template 456 | volatile Node* ShmHashTable::Find(const Key& key, Arg&& arg) { 457 | assert(shm_.is_initialized()); 458 | size_t hash_code = std::hash()(key); 459 | for (size_t r = 0; r < shm_->row_num; r++) { 460 | size_t index = GetIndex(r, hash_code % row_mods_[r]); 461 | volatile Node* node = &shm_->nodes[index]; 462 | auto key_info = node->Key(std::forward(arg)); 463 | if (key_info.first && key == key_info.second) { 464 | return node; 465 | } 466 | } 467 | return nullptr; 468 | } 469 | 470 | template 471 | template 472 | volatile Node* ShmHashTable::FindOrAlloc(const Key& key, 473 | Arg&& arg, 474 | bool* is_found) { 475 | assert(shm_.is_initialized()); 476 | volatile Node* empty_node = nullptr; 477 | if (is_found) *is_found = false; 478 | size_t hash_code = std::hash()(key); 479 | for (size_t r = 0; r < shm_->row_num; r++) { 480 | size_t index = GetIndex(r, hash_code % row_mods_[r]); 481 | volatile Node* node = &shm_->nodes[index]; 482 | auto key_info = node->Key(std::forward(arg)); 483 | if (key_info.first) { // not empty 484 | if (key == key_info.second) { 485 | if (is_found) *is_found = true; 486 | return node; 487 | } 488 | } else if (!empty_node) { // emtpy 489 | empty_node = node; 490 | if (r > shm_->max_row_touched) 491 | shm_->max_row_touched = r; 492 | } 493 | } 494 | return empty_node; 495 | } 496 | 497 | template 498 | bool ShmHashTable::Travel(TravelPos* pos, 499 | size_t max_travel_nodes, 500 | std::function f) { 501 | assert(shm_.is_initialized()); 502 | size_t count = 0; 503 | for (size_t r = 0; r < shm_->row_num; r++) { 504 | for (size_t c = 0; c < row_mods_[r]; c++) { 505 | if (r == 0 && c == 0) { 506 | r = pos->row; 507 | c = pos->col; 508 | } 509 | if (max_travel_nodes > 0 && count >= max_travel_nodes) { 510 | pos->row = r; 511 | pos->col = c; 512 | return true; 513 | } 514 | size_t index = GetIndex(r, c); 515 | volatile Node* node = &shm_->nodes[index]; 516 | if (node->Key().first) { 517 | f(node); 518 | } 519 | count++; 520 | } 521 | } 522 | pos->row = 0; 523 | pos->col = 0; 524 | return true; 525 | } 526 | 527 | } // namespace shmc 528 | 529 | #endif // SHMC_SHM_HASH_TABLE_H_ 530 | -------------------------------------------------------------------------------- /src/shmc/shm_hash_table_m.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_SHM_HASH_TABLE_M_H_ 31 | #define SHMC_SHM_HASH_TABLE_M_H_ 32 | 33 | #include 34 | #include 35 | #include 36 | #include "shmc/shm_handle.h" 37 | #include "shmc/common_utils.h" 38 | 39 | namespace shmc { 40 | 41 | /* A shm implementation of hash table (classic version, memory effecient) 42 | * @Key type of search key, any type supporting =,==,std::hash 43 | * @Node type of node which contains both key and value infomation 44 | * @Alloc shm allocator to use [SVIPC(default), SVIPC_HugeTLB, POSIX, ANON, HEAP] 45 | * 46 | * This hash table implementation uses a pre-allocated array of fixed-size 47 | * nodes in shm. The array of nodes are treated as a ROW * COL matrix and the 48 | * hash colision is solved by rehashes on different rows. Compared to 49 | * this container can provides better performance of memory 50 | * usage (>90% if row_num > 50), but it is slower than the former one. 51 | * Normally as default hash-table container is all you need. 52 | * 53 | * Read-Write concurrency safety should be considered by the user, for example 54 | * race conditions such as key-readying by Node::Key() and key-writing of the 55 | * same node, such as value-reading and value-updating of the same node. 56 | * 57 | * This is a low-level container, consider use first. 58 | * 59 | * Param @Node can be any StandardLayoutType with a get-key method as below: 60 | * pair Node::Key() const; 61 | * first bool element of returned pair indicates whether the node is used, 62 | * and if true the second element holds the key of the node. 63 | */ 64 | template 65 | class ShmHashTableM { 66 | public: 67 | ShmHashTableM() = default; 68 | 69 | /* Initializer for READ & WRITE 70 | * @shm_key key or name of the shm to attach or create 71 | * @col_num columns of nodes, normally use capacity / row_num 72 | * @row_num rows of nodes, 50 is a recommanded value 73 | * 74 | * This initializer should be called by the container writer/owner and 75 | * it will try to attach an existing shm or created a new one if needed. 76 | * 77 | * @return true if succeed 78 | */ 79 | bool InitForWrite(const std::string& shm_key, 80 | size_t col_num, size_t row_num, 81 | void* user_arg = nullptr); 82 | 83 | /* Initializer for READ-ONLY 84 | * @shm_key key or name of the shm to attach 85 | * 86 | * This initializer should be called by the container reader and 87 | * it will try to attach an existing shm with read-only mode. 88 | * 89 | * @return true if succeed 90 | */ 91 | bool InitForRead(const std::string& shm_key, 92 | void* user_arg = nullptr); 93 | 94 | /* Find node by key 95 | * @key key to find 96 | * 97 | * This const method returns pointer to const node (read-only) if found. 98 | * 99 | * @return pointer to node if found, otherwise nullptr 100 | */ 101 | const volatile Node* Find(const Key& key) const; 102 | 103 | /* Find node by key 104 | * @key key to find 105 | * 106 | * This method returns pointer to non-const node if found and user can 107 | * update the content of the node. Note that Read-Write concurrency safety 108 | * should be considered. 109 | * 110 | * @return pointer to node if found, otherwise nullptr 111 | */ 112 | volatile Node* Find(const Key& key); 113 | 114 | /* Find node by key or allocate a new node 115 | * @key key to find 116 | * @is_found [out] whether the node is found by key if non-nullptr returned 117 | * 118 | * If a node with the key is found it returns pointer to the node, otherwise 119 | * it will find an empty node to return, if no empty node can be found it 120 | * returns nullptr. If this method returns non-nullptr is_found indicates 121 | * whether the node returned is found by key. 122 | * 123 | * @return pointer to the found node or empty node or nullptr 124 | */ 125 | volatile Node* FindOrAlloc(const Key& key, bool *is_found); 126 | 127 | /* Cursor type recording current travelling position 128 | */ 129 | struct TravelPos { 130 | uint32_t row; 131 | uint32_t col; 132 | TravelPos(): row(0), col(0) {} 133 | bool at_origin() { 134 | return (row == 0 && col == 0); 135 | } 136 | }; 137 | 138 | /* Travel nodes 139 | * @pos [in|out] current position of travel 140 | * @max_travel_nodes max nodes to travel in this method call, 0 for all 141 | * @f callback to be called when visiting each node 142 | * 143 | * This method will scan array of nodes and find non-empty nodes and 144 | * call the callback @f for each one. If @max_travel_nodes nodes have 145 | * been travelled this method will stop and save the position to @pos. 146 | * 147 | * @return true if succeed 148 | */ 149 | bool Travel(TravelPos* pos, size_t max_travel_nodes, 150 | std::function f); 151 | 152 | /* Travel all nodes 153 | * @f callback to be called when visiting each node 154 | * 155 | * This method will scan array of nodes and find all non-empty nodes and 156 | * call the callback @f for each one. 157 | * 158 | * @return true if succeed 159 | */ 160 | bool Travel(std::function f) { 161 | TravelPos pos; 162 | return Travel(&pos, 0, std::move(f)); 163 | } 164 | 165 | /* getter 166 | * 167 | * @return ideal capacity of the hash-table 168 | */ 169 | size_t ideal_capacity() const { 170 | return capacity_; 171 | } 172 | 173 | /* getter 174 | * 175 | * The expected actual capacity is about 90% of ideal. 176 | * 177 | * @return expected capacity of the hash-table 178 | */ 179 | size_t expected_capacity() const { 180 | return ideal_capacity() * 90 / 100; 181 | } 182 | 183 | private: 184 | size_t GetIndex(size_t row, size_t col) const { 185 | return shm_->col_num * row + col; 186 | } 187 | 188 | private: 189 | SHMC_NOT_COPYABLE_AND_MOVABLE(ShmHashTableM); 190 | 191 | struct ShmHead { 192 | volatile uint64_t magic; 193 | volatile uint32_t ver; 194 | volatile uint32_t head_size; 195 | volatile uint32_t node_size; 196 | volatile uint32_t row_num; 197 | volatile uint32_t col_num; 198 | volatile uint32_t max_row_touched; 199 | volatile uint64_t create_time; 200 | volatile char reserved[64]; 201 | volatile Node nodes[0]; 202 | } __attribute__((__packed__)); 203 | static_assert(sizeof(ShmHead) == 104, "unexpected ShmHead layout"); 204 | static_assert(alignof(Node) <= 8, "align requirement of Node can't be met"); 205 | 206 | ShmHandle shm_; 207 | 208 | // 50-rows is often a well performed setting 209 | static const size_t kMinRows = 10; 210 | static const size_t kMaxRows = 200; 211 | size_t row_mods_[kMaxRows]; 212 | size_t capacity_ = 0; 213 | void* user_arg_ = nullptr; 214 | }; 215 | 216 | template 217 | bool ShmHashTableM::InitForWrite(const std::string& shm_key, 218 | size_t col_num, 219 | size_t row_num, 220 | void* user_arg) { 221 | if (shm_.is_initialized()) { 222 | SHMC_ERR_RET("ShmHashTableM::InitForWrite: already initialized\n"); 223 | } 224 | if (row_num > kMaxRows || row_num < kMinRows || col_num <= row_num) { 225 | SHMC_ERR_RET("ShmHashTableM::InitForWrite: bad col_num or row_num\n"); 226 | } 227 | size_t node_size = sizeof(Node); 228 | size_t shm_size = sizeof(ShmHead) + node_size * col_num * row_num; 229 | user_arg_ = user_arg; 230 | if (!Utils::GetPrimeArray(col_num, row_num, row_mods_)) { 231 | SHMC_ERR_RET("ShmHashTableM::InitForWrite: get_prime_array fail\n"); 232 | } 233 | capacity_ = 0; 234 | for (size_t i = 0; i < row_num; i++) { 235 | capacity_ += row_mods_[i]; 236 | } 237 | if (!shm_.InitForWrite(shm_key, shm_size, Utils::DefaultCreateFlags())) { 238 | SHMC_ERR_RET("ShmHashTableM::InitForWrite: shm init(%s, %lu) fail\n", 239 | shm_key.c_str(), shm_size); 240 | } 241 | uint64_t now_ts = time(NULL); 242 | if (shm_.is_newly_created()) { 243 | shm_->magic = Utils::GenMagic("HashTab"); 244 | shm_->ver = Utils::MakeVer(1, 0); 245 | shm_->head_size = sizeof(ShmHead); 246 | shm_->node_size = node_size; 247 | shm_->row_num = row_num; 248 | shm_->col_num = col_num; 249 | shm_->create_time = now_ts; 250 | shm_->max_row_touched = 0; 251 | } else { 252 | if (shm_->magic != Utils::GenMagic("HashTab")) 253 | SHMC_ERR_RET("ShmHashTableM::InitForWrite: bad magic(0x%lx)\n", shm_->magic); 254 | if (Utils::MajorVer(shm_->ver) != 1) 255 | SHMC_ERR_RET("ShmHashTableM::InitForWrite: bad ver(0x%x)\n", shm_->ver); 256 | if (shm_->head_size != sizeof(ShmHead)) 257 | SHMC_ERR_RET("ShmHashTableM::InitForWrite: bad head_size(%u)\n", shm_->head_size); 258 | if (shm_->node_size != node_size) 259 | SHMC_ERR_RET("ShmHashTableM::InitForWrite: bad node_size(%u)\n", shm_->node_size); 260 | if (shm_->row_num != row_num) 261 | SHMC_ERR_RET("ShmHashTableM::InitForWrite: bad row_num(%u)\n", shm_->row_num); 262 | if (shm_->col_num != col_num) 263 | SHMC_ERR_RET("ShmHashTableM::InitForWrite: bad col_num(%u)\n", shm_->col_num); 264 | } 265 | return true; 266 | } 267 | 268 | template 269 | bool ShmHashTableM::InitForRead(const std::string& shm_key, 270 | void* user_arg) { 271 | if (shm_.is_initialized()) { 272 | SHMC_ERR_RET("ShmHashTableM::InitForRead: already initialized\n"); 273 | } 274 | size_t node_size = sizeof(Node); 275 | user_arg_ = user_arg; 276 | if (!shm_.InitForRead(shm_key, sizeof(ShmHead))) { 277 | SHMC_ERR_RET("ShmHashTableM::InitForRead: shm init(%s, %lu) fail\n", 278 | shm_key.c_str(), sizeof(ShmHead)); 279 | } 280 | if (shm_->magic != Utils::GenMagic("HashTab")) 281 | SHMC_ERR_RET("ShmHashTableM::InitForRead: bad magic(0x%lx)\n", shm_->magic); 282 | if (Utils::MajorVer(shm_->ver) != 1) 283 | SHMC_ERR_RET("ShmHashTableM::InitForRead: bad ver(0x%x)\n", shm_->ver); 284 | if (shm_->head_size != sizeof(ShmHead)) 285 | SHMC_ERR_RET("ShmHashTableM::InitForRead: bad head_size(%u)\n", shm_->head_size); 286 | if (shm_->node_size != node_size) 287 | SHMC_ERR_RET("ShmHashTableM::InitForRead: bad node_size(%u)\n", shm_->node_size); 288 | size_t shm_size = sizeof(ShmHead) + node_size * shm_->col_num * shm_->row_num; 289 | if (!shm_.CheckSize(shm_size)) 290 | SHMC_ERR_RET("ShmHashTableM::InitForRead: bad shm size(%u)\n", shm_.size()); 291 | if (!Utils::GetPrimeArray(shm_->col_num, shm_->row_num, row_mods_)) { 292 | SHMC_ERR_RET("ShmHashTableM::InitForRead: GetPrimeArray fail"); 293 | } 294 | capacity_ = 0; 295 | for (size_t i = 0; i < shm_->row_num; i++) { 296 | capacity_ += row_mods_[i]; 297 | } 298 | return true; 299 | } 300 | 301 | template 302 | const volatile Node* ShmHashTableM::Find(const Key& key) const { 303 | assert(shm_.is_initialized()); 304 | size_t hash_code = std::hash()(key); 305 | for (size_t r = 0; r < shm_->row_num; r++) { 306 | size_t index = GetIndex(r, hash_code % row_mods_[r]); 307 | const volatile Node* node = &shm_->nodes[index]; 308 | auto key_info = node->Key(); 309 | if (key_info.first && key == key_info.second) { 310 | return node; 311 | } 312 | } 313 | return nullptr; 314 | } 315 | 316 | template 317 | volatile Node* ShmHashTableM::Find(const Key& key) { 318 | assert(shm_.is_initialized()); 319 | size_t hash_code = std::hash()(key); 320 | for (size_t r = 0; r < shm_->row_num; r++) { 321 | size_t index = GetIndex(r, hash_code % row_mods_[r]); 322 | volatile Node* node = &shm_->nodes[index]; 323 | auto key_info = node->Key(); 324 | if (key_info.first && key == key_info.second) { 325 | return node; 326 | } 327 | } 328 | return nullptr; 329 | } 330 | 331 | template 332 | volatile Node* ShmHashTableM::FindOrAlloc(const Key& key, 333 | bool* is_found) { 334 | assert(shm_.is_initialized()); 335 | volatile Node* empty_node = nullptr; 336 | if (is_found) *is_found = false; 337 | size_t hash_code = std::hash()(key); 338 | for (size_t r = 0; r < shm_->row_num; r++) { 339 | size_t index = GetIndex(r, hash_code % row_mods_[r]); 340 | volatile Node* node = &shm_->nodes[index]; 341 | auto key_info = node->Key(); 342 | if (key_info.first) { // not empty 343 | if (key == key_info.second) { 344 | if (is_found) *is_found = true; 345 | return node; 346 | } 347 | } else if (!empty_node) { // emtpy 348 | empty_node = node; 349 | if (r > shm_->max_row_touched) 350 | shm_->max_row_touched = r; 351 | } 352 | } 353 | return empty_node; 354 | } 355 | 356 | template 357 | bool ShmHashTableM::Travel(TravelPos* pos, 358 | size_t max_travel_nodes, 359 | std::function f) { 360 | assert(shm_.is_initialized()); 361 | size_t count = 0; 362 | for (size_t r = 0; r < shm_->row_num; r++) { 363 | for (size_t c = 0; c < row_mods_[r]; c++) { 364 | if (r == 0 && c == 0) { 365 | r = pos->row; 366 | c = pos->col; 367 | } 368 | if (max_travel_nodes > 0 && count >= max_travel_nodes) { 369 | pos->row = r; 370 | pos->col = c; 371 | return true; 372 | } 373 | size_t index = GetIndex(r, c); 374 | volatile Node* node = &shm_->nodes[index]; 375 | if (node->Key().first) { 376 | f(node); 377 | } 378 | count++; 379 | } 380 | } 381 | pos->row = 0; 382 | pos->col = 0; 383 | return true; 384 | } 385 | 386 | } // namespace shmc 387 | 388 | #endif // SHMC_SHM_HASH_TABLE_M_H_ 389 | -------------------------------------------------------------------------------- /src/shmc/shm_queue.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_SHM_QUEUE_H_ 31 | #define SHMC_SHM_QUEUE_H_ 32 | 33 | #include 34 | #include 35 | #include 36 | #include "shmc/shm_handle.h" 37 | #include "shmc/common_utils.h" 38 | 39 | namespace shmc { 40 | 41 | /* Descriptor of internal buffer of ShmQueue for zero-copy 42 | * 43 | * This descriptor containing a pointer and a length points to the 44 | * internal buffer of the queue which can be directly read or written. 45 | */ 46 | struct ZeroCopyBuf { 47 | void* ptr; 48 | size_t len; 49 | }; 50 | 51 | /* A one-writer-one-reader FIFO queue 52 | * @Alloc shm allocator to use [SVIPC(default), SVIPC_HugeTLB, POSIX, ANON, HEAP] 53 | * 54 | * ShmQueue is implemented as a FIFO queue of fixed size. One writer/producer 55 | * is assumed to push new data to the tail of the queue, and one 56 | * reader/consumer can read concurrently from the head of the queue. If 57 | * producing of the writer is much faster than consuming of the reader the 58 | * queue would be full and the writer would fail to push data until more data 59 | * is popped. 60 | * 61 | * Read-Write concurrency safety is offered internally in a lock-free manner, 62 | * but Write-Write or Read-Read is not. If you have multi-readers or 63 | * multi-writers you can either 1) create a group of ShmQueue or 2) using 64 | * external locks. 65 | */ 66 | template 67 | class ShmQueue { 68 | public: 69 | ShmQueue() = default; 70 | 71 | /* Initializer for write (as producer) 72 | * @shm_key key or name of the shm to attach or create 73 | * @buf_size_bytes size of the sync buffer 74 | * 75 | * This initializer should be called by the container producer and 76 | * it will try to attach an existing shm or created a new one if needed. 77 | * 78 | * @return true if succeed 79 | */ 80 | bool InitForWrite(const std::string& shm_key, 81 | size_t buf_size_bytes); 82 | 83 | /* Initializer for read (as consumer) 84 | * @shm_key key or name of the shm to attach 85 | * 86 | * This initializer should be called by the queue consumer and 87 | * it will try to attach an existing shm. 88 | * 89 | * @return true if succeed 90 | */ 91 | bool InitForRead(const std::string& shm_key); 92 | 93 | /* Push data to the queue 94 | * @buf pointer to the data to be pushed 95 | * @len size of @buf 96 | * 97 | * Push data of buffer to the queue unless the queue is full. 98 | * 99 | * @return true if succeed 100 | */ 101 | bool Push(const void* buf, size_t len); 102 | 103 | /* Push data to the queue 104 | * @data data to be pushed 105 | * 106 | * Push data of string to the queue unless the queue is full. 107 | * 108 | * @return true if succeed 109 | */ 110 | bool Push(const std::string& data) { 111 | return Push(data.data(), data.size()); 112 | } 113 | 114 | /* Pop data from the queue 115 | * @buf [out] pointer to the buffer for popped data 116 | * @len [in|out] pointer to length of the buffer 117 | * 118 | * Pop data from the queue unless the queue is empty. As input param *@len 119 | * should be the allocated length of @buf, and when the function returns 120 | * successfully *@len records the actual length of data popped. 121 | * 122 | * @return true if succeed 123 | */ 124 | bool Pop(void* buf, size_t* len); 125 | 126 | /* Pop data from the queue 127 | * @data [out] pointer to data popped 128 | * 129 | * Pop data from the queue unless the queue is empty. 130 | * 131 | * @return true if succeed 132 | */ 133 | bool Pop(std::string* data); 134 | 135 | /* Prepare step of push data in a zero-copy way 136 | * @len request size of the buffer to write 137 | * @zcb [out] returned descriptor of the buffer to write 138 | * 139 | * This method first checks for free space of size @len and then returned 140 | * the @zcb pointer to the write buffer. It does not change state of the 141 | * queue so it is an idempotent operation. 142 | * 143 | * @return true if succeed 144 | */ 145 | bool ZeroCopyPushPrepare(size_t len, ZeroCopyBuf* zcb); 146 | 147 | /* Commit step of push data in a zero-copy way 148 | * @zcb buffer descriptor returned by the prepare step 149 | * 150 | * Before calling this you should call prepare step to get the buffer to 151 | * write and then filling the buffer in any way. This method will check 152 | * integrity of the descriptor and then forward tail-pointer of the queue 153 | * to append the buffer into the queue. For each push this method should 154 | * be called only once. 155 | * 156 | * @return true if succeed 157 | */ 158 | bool ZeroCopyPushCommit(const ZeroCopyBuf& zcb); 159 | 160 | /* Prepare step of pop data in a zero-copy way 161 | * @zcb [out] returned descriptor of the buffer to read 162 | * 163 | * This method checks for first data-node at the head of the queue and then 164 | * returns the @zcb pointer to the data if found. It does not change state 165 | * of the queue so it is an idempotent operation. 166 | * 167 | * @return true if succeed 168 | */ 169 | bool ZeroCopyPopPrepare(ZeroCopyBuf* zcb); 170 | 171 | /* Commit step of pop data in a zero-copy way 172 | * @zcb buffer descriptor returned by the prepare step 173 | * 174 | * Before calling this you should call prepare step to get the buffer to 175 | * read and then reading the buffer in any way. This method will check 176 | * integrity of the descriptor and then forward head-pointer of the queue 177 | * to remove the buffer from the queue. For each pop this method should 178 | * be called only once. 179 | * 180 | * @return true if succeed 181 | */ 182 | bool ZeroCopyPopCommit(const ZeroCopyBuf& zcb); 183 | 184 | private: 185 | SHMC_NOT_COPYABLE_AND_MOVABLE(ShmQueue); 186 | 187 | struct ShmHead { 188 | volatile uint64_t magic; 189 | volatile uint32_t ver; 190 | volatile uint32_t head_size; 191 | volatile uint32_t align_size; 192 | volatile uint32_t padding1; 193 | volatile uint64_t head_pos; 194 | volatile uint64_t tail_pos; 195 | volatile uint64_t create_time; 196 | volatile uint64_t queue_buf_size; 197 | volatile uint8_t reserved[72]; 198 | volatile uint8_t queue_buf[0]; 199 | } __attribute__((__packed__)); 200 | static_assert(sizeof(ShmHead) == 128, "unexpected ShmHead layout"); 201 | 202 | static constexpr size_t kAlignSize = 64; 203 | static constexpr uint16_t kStartTag = 0x9cec; 204 | 205 | enum NodeType { 206 | kDataNode = 1, kEndFlagNode = 2 207 | }; 208 | 209 | struct NodeHead { 210 | volatile uint16_t start_tag; 211 | volatile uint8_t type; 212 | volatile uint8_t flags; 213 | volatile uint32_t len; 214 | volatile uint8_t data[0]; 215 | } __attribute__((__packed__)); 216 | 217 | static_assert(sizeof(ShmHead) % kAlignSize == 0, "ShmHead size must align"); 218 | static_assert(sizeof(NodeHead) <= kAlignSize, "NodeHead > kAlignSize"); 219 | static_assert(sizeof(ShmHead) == 128, "unexpected ShmHead size"); 220 | static_assert(sizeof(NodeHead) == 8, "unexpected NodeHead size"); 221 | 222 | ShmHandle shm_; 223 | 224 | private: 225 | void ZeroCopyPushCommitUnsafe(const ZeroCopyBuf& zcb); 226 | void ZeroCopyPopCommitUnsafe(const ZeroCopyBuf& zcb); 227 | 228 | static size_t GetNodeSize(size_t node_data_len) { 229 | return Utils::RoundAlign(sizeof(NodeHead) + node_data_len); 230 | } 231 | 232 | volatile NodeHead* GetNodeHead(uint64_t pos) { 233 | return reinterpret_cast(shm_->queue_buf + pos); 234 | } 235 | }; 236 | 237 | template 238 | bool ShmQueue::InitForWrite(const std::string& shm_key, 239 | size_t buf_size_bytes) { 240 | if (shm_.is_initialized()) { 241 | SHMC_ERR_RET("ShmQueue::InitForWrite: already initialized\n"); 242 | } 243 | if (buf_size_bytes < 1024) { 244 | SHMC_ERR_RET("ShmQueue::InitForWrite: invalid buf_size_bytes\n"); 245 | } 246 | buf_size_bytes = Utils::RoundAlign(buf_size_bytes); 247 | size_t shm_size = sizeof(ShmHead) + buf_size_bytes; 248 | if (!shm_.InitForWrite(shm_key, shm_size, Utils::DefaultCreateFlags())) { 249 | SHMC_ERR_RET("ShmQueue::InitForWrite: shm_.InitForWrite(%s, %lu) fail\n", 250 | shm_key.c_str(), shm_size); 251 | return false; 252 | } 253 | uint64_t now_ts = time(NULL); 254 | if (shm_.is_newly_created()) { 255 | shm_->magic = Utils::GenMagic("Queue"); 256 | shm_->ver = Utils::MakeVer(1, 0); 257 | shm_->head_size = sizeof(ShmHead); 258 | shm_->align_size = kAlignSize; 259 | shm_->head_pos = 0; 260 | shm_->tail_pos = 0; 261 | shm_->create_time = now_ts; 262 | shm_->queue_buf_size = buf_size_bytes; 263 | } else { 264 | if (shm_->magic != Utils::GenMagic("Queue")) 265 | SHMC_ERR_RET("ShmQueue::InitForWrite: bad magic(0x%lx)\n", shm_->magic); 266 | if (Utils::MajorVer(shm_->ver) != 1) 267 | SHMC_ERR_RET("ShmQueue::InitForWrite: bad ver(0x%x)\n", shm_->ver); 268 | if (shm_->head_size != sizeof(ShmHead)) 269 | SHMC_ERR_RET("ShmQueue::InitForWrite: bad head_size(%u)\n", 270 | shm_->head_size); 271 | if (shm_->align_size != kAlignSize) 272 | SHMC_ERR_RET("ShmQueue::InitForWrite: bad align_size(%u)\n", 273 | shm_->align_size); 274 | if ((shm_->head_pos & (kAlignSize - 1)) != 0 || 275 | (shm_->tail_pos & (kAlignSize - 1)) != 0 || 276 | (shm_->queue_buf_size & (kAlignSize - 1)) != 0) { 277 | SHMC_ERR_RET("ShmQueue::InitForWrite: bad alignment(%u,%u,%lu)\n", 278 | shm_->head_pos, shm_->tail_pos, shm_->queue_buf_size); 279 | } 280 | if (shm_->head_pos >= shm_->queue_buf_size || 281 | shm_->tail_pos >= shm_->queue_buf_size) { 282 | SHMC_ERR_RET("ShmQueue::InitForWrite: bad head/tail_pos(%u,%u,%lu)\n", 283 | shm_->head_pos, shm_->tail_pos, shm_->queue_buf_size); 284 | } 285 | } 286 | return true; 287 | } 288 | 289 | template 290 | bool ShmQueue::InitForRead(const std::string& shm_key) { 291 | if (shm_.is_initialized()) { 292 | SHMC_ERR_RET("ShmQueue::InitForRead: already initialized\n"); 293 | } 294 | if (!shm_.InitForWrite(shm_key, sizeof(ShmHead), kNoCreate)) { 295 | SHMC_ERR_RET("ShmQueue::InitForRead: shm_.InitForRead(%s, %lu) fail\n", 296 | shm_key.c_str(), sizeof(ShmHead)); 297 | } 298 | if (shm_->magic != Utils::GenMagic("Queue")) 299 | SHMC_ERR_RET("ShmQueue::InitForRead: bad magic(0x%lx)\n", shm_->magic); 300 | if (Utils::MajorVer(shm_->ver) != 1) 301 | SHMC_ERR_RET("ShmQueue::InitForRead: bad ver(0x%x)\n", shm_->ver); 302 | if (shm_->head_size != sizeof(ShmHead)) 303 | SHMC_ERR_RET("ShmQueue::InitForRead: bad head_size(%u)\n", shm_->head_size); 304 | if (shm_->align_size != kAlignSize) 305 | SHMC_ERR_RET("ShmQueue::InitForWrite: bad align_size(%u)\n", 306 | shm_->align_size); 307 | if ((shm_->head_pos & (kAlignSize - 1)) != 0 || 308 | (shm_->tail_pos & (kAlignSize - 1)) != 0 || 309 | (shm_->queue_buf_size & (kAlignSize - 1)) != 0) { 310 | SHMC_ERR_RET("ShmQueue::InitForWrite: bad alignment(%u,%u,%lu)\n", 311 | shm_->head_pos, shm_->tail_pos, shm_->queue_buf_size); 312 | } 313 | if (shm_->head_pos >= shm_->queue_buf_size || 314 | shm_->tail_pos >= shm_->queue_buf_size) { 315 | SHMC_ERR_RET("ShmQueue::InitForWrite: bad head/tail_pos(%u,%u,%lu)\n", 316 | shm_->head_pos, shm_->tail_pos, shm_->queue_buf_size); 317 | } 318 | size_t shm_size = sizeof(ShmHead) + shm_->queue_buf_size; 319 | if (!shm_.CheckSize(shm_size)) 320 | SHMC_ERR_RET("ShmQueue::InitForRead: bad shm size(%u)\n", shm_.size()); 321 | return true; 322 | } 323 | 324 | template 325 | bool ShmQueue::Push(const void* buf, size_t len) { 326 | assert(shm_.is_initialized()); 327 | ZeroCopyBuf zcb; 328 | if (!ZeroCopyPushPrepare(len, &zcb)) { 329 | return false; 330 | } 331 | memcpy(zcb.ptr, buf, len); 332 | ZeroCopyPushCommitUnsafe(zcb); 333 | return true; 334 | } 335 | 336 | template 337 | bool ShmQueue::Pop(void* buf, size_t* len) { 338 | assert(shm_.is_initialized()); 339 | ZeroCopyBuf zcb; 340 | if (!ZeroCopyPopPrepare(&zcb)) { 341 | return false; 342 | } 343 | if (len) { 344 | if (*len < zcb.len) { 345 | return false; 346 | } 347 | if (buf) { 348 | memcpy(buf, zcb.ptr, zcb.len); 349 | } 350 | *len = zcb.len; 351 | } 352 | ZeroCopyPopCommitUnsafe(zcb); 353 | return true; 354 | } 355 | 356 | template 357 | bool ShmQueue::Pop(std::string* data) { 358 | assert(shm_.is_initialized()); 359 | ZeroCopyBuf zcb; 360 | if (!ZeroCopyPopPrepare(&zcb)) { 361 | return false; 362 | } 363 | if (data) { 364 | data->assign(static_cast(zcb.ptr), zcb.len); 365 | } 366 | ZeroCopyPopCommitUnsafe(zcb); 367 | return true; 368 | } 369 | 370 | template 371 | bool ShmQueue::ZeroCopyPushPrepare(size_t len, ZeroCopyBuf* zcb) { 372 | assert(shm_.is_initialized()); 373 | uint64_t head_pos = shm_->head_pos; 374 | uint64_t tail_pos = shm_->tail_pos; 375 | uint64_t queue_size = shm_->queue_buf_size; 376 | assert((head_pos & (kAlignSize - 1)) == 0); 377 | assert((tail_pos & (kAlignSize - 1)) == 0); 378 | assert((queue_size & (kAlignSize - 1)) == 0); 379 | uint64_t node_len = GetNodeSize(len); 380 | uint64_t append_tail_pos = tail_pos + node_len; 381 | uint64_t write_pos; 382 | if (head_pos <= tail_pos) { 383 | // by design limit: tail_pos never get to 0 again except initial state 384 | // this limit makes the code simpler and cleaner 385 | if (append_tail_pos < queue_size) { 386 | // append tail 387 | write_pos = tail_pos; 388 | } else if (node_len < head_pos) { 389 | // wrap round 390 | write_pos = 0; 391 | // add end-flag-node 392 | assert(tail_pos + sizeof(NodeHead) <= queue_size); 393 | volatile NodeHead* node_head = GetNodeHead(tail_pos); 394 | node_head->start_tag = kStartTag; 395 | node_head->type = static_cast(NodeType::kEndFlagNode); 396 | node_head->flags = 0; 397 | node_head->len = queue_size - tail_pos - sizeof(NodeHead); 398 | } else { 399 | // no space 400 | return false; 401 | } 402 | } else { 403 | if (append_tail_pos < head_pos) { 404 | // append tail 405 | write_pos = tail_pos; 406 | } else { 407 | // no space 408 | return false; 409 | } 410 | } 411 | if (zcb) { 412 | zcb->ptr = const_cast(GetNodeHead(write_pos)->data); 413 | zcb->len = len; 414 | } 415 | return true; 416 | } 417 | 418 | template 419 | inline void ShmQueue::ZeroCopyPushCommitUnsafe(const ZeroCopyBuf& zcb) { 420 | volatile NodeHead* node_head = reinterpret_cast( 421 | static_cast(zcb.ptr) - sizeof(NodeHead)); 422 | node_head->start_tag = kStartTag; 423 | node_head->type = static_cast(NodeType::kDataNode); 424 | node_head->flags = 0; 425 | node_head->len = zcb.len; 426 | // tail_pos never get to 0 except initial state because of our limit by 427 | // design, so need not deal with wrap round condition here 428 | shm_->tail_pos = Utils::RoundAlign( 429 | static_cast(zcb.ptr) + zcb.len - shm_->queue_buf); 430 | } 431 | 432 | template 433 | bool ShmQueue::ZeroCopyPushCommit(const ZeroCopyBuf& zcb) { 434 | assert(shm_.is_initialized()); 435 | uint64_t head_pos = shm_->head_pos; 436 | uint64_t tail_pos = shm_->tail_pos; 437 | uint64_t queue_size = shm_->queue_buf_size; 438 | 439 | assert((head_pos & (kAlignSize - 1)) == 0); 440 | assert((tail_pos & (kAlignSize - 1)) == 0); 441 | assert((queue_size & (kAlignSize - 1)) == 0); 442 | assert(head_pos < queue_size && tail_pos < queue_size); 443 | assert(zcb.ptr); 444 | 445 | uint64_t write_pos = static_cast(zcb.ptr) - sizeof(NodeHead) 446 | - shm_->queue_buf; 447 | size_t write_len = GetNodeSize(zcb.len); 448 | 449 | if (write_pos == tail_pos) { 450 | if (write_pos + write_len >= (head_pos <= tail_pos ? queue_size 451 | : head_pos)) { 452 | Utils::Log(kWarning, "ShmQueue::ZeroCopyPushCommit: overflow\n"); 453 | return false; 454 | } 455 | } else if (write_pos == 0) { 456 | volatile NodeHead* node_head = GetNodeHead(tail_pos); 457 | if (node_head->start_tag != kStartTag || 458 | node_head->type != NodeType::kEndFlagNode) { 459 | Utils::Log(kWarning, "ShmQueue::ZeroCopyPushCommit: bad end-flag-node\n"); 460 | return false; 461 | } 462 | if (write_len >= head_pos) { 463 | Utils::Log(kWarning, "ShmQueue::ZeroCopyPushCommit: overflow\n"); 464 | return false; 465 | } 466 | } else { 467 | // invalid pos 468 | Utils::Log(kWarning, "ShmQueue::ZeroCopyPushCommit: invalid pos\n"); 469 | return false; 470 | } 471 | 472 | ZeroCopyPushCommitUnsafe(zcb); 473 | return true; 474 | } 475 | 476 | template 477 | bool ShmQueue::ZeroCopyPopPrepare(ZeroCopyBuf* zcb) { 478 | assert(shm_.is_initialized()); 479 | uint64_t head_pos = shm_->head_pos; 480 | uint64_t tail_pos = shm_->tail_pos; 481 | uint64_t queue_size = shm_->queue_buf_size; 482 | 483 | assert((head_pos & (kAlignSize - 1)) == 0); 484 | assert((tail_pos & (kAlignSize - 1)) == 0); 485 | assert((queue_size & (kAlignSize - 1)) == 0); 486 | assert(head_pos < queue_size && tail_pos < queue_size); 487 | 488 | if (head_pos == tail_pos) { 489 | // the queue is empty 490 | return false; 491 | } 492 | volatile NodeHead* node_head = GetNodeHead(head_pos); 493 | if (node_head->start_tag != kStartTag) { 494 | SHMC_ERR_RET("ShmQueue::ZeroCopyPopPrepare: bad start-tag(%hu) at %lu\n", 495 | node_head->start_tag, head_pos); 496 | } 497 | // wrap round condition 498 | if (node_head->type == NodeType::kEndFlagNode) { 499 | if (tail_pos == 0) { // never happen by design 500 | SHMC_ERR_RET("ShmQueue::ZeroCopyPopPrepare: tail_pos at 0 again\n"); 501 | } 502 | head_pos = 0; 503 | node_head = GetNodeHead(head_pos); 504 | if (node_head->start_tag != kStartTag) { 505 | SHMC_ERR_RET("ShmQueue::ZeroCopyPopPrepare: bad start-tag(%hu) @%lu\n", 506 | node_head->start_tag, head_pos); 507 | } 508 | } 509 | // data-node expected 510 | if (node_head->type != NodeType::kDataNode) { 511 | SHMC_ERR_RET("ShmQueue::ZeroCopyPopPrepare: bad node type(%hhu) @%lu\n", 512 | node_head->type, head_pos); 513 | } 514 | if (head_pos + GetNodeSize(node_head->len) > 515 | (head_pos <= tail_pos ? tail_pos : queue_size - kAlignSize)) { 516 | SHMC_ERR_RET("ShmQueue::ZeroCopyPopPrepare: bad node len(%u) " 517 | "pos(%lu,%lu,%lu)\n", node_head->len, head_pos, tail_pos, queue_size); 518 | } 519 | if (zcb) { 520 | zcb->ptr = const_cast(node_head->data); 521 | zcb->len = node_head->len; 522 | } 523 | return true; 524 | } 525 | 526 | template 527 | inline void ShmQueue::ZeroCopyPopCommitUnsafe(const ZeroCopyBuf& zcb) { 528 | shm_->head_pos = Utils::RoundAlign( 529 | static_cast(zcb.ptr) + zcb.len - shm_->queue_buf); 530 | } 531 | 532 | template 533 | bool ShmQueue::ZeroCopyPopCommit(const ZeroCopyBuf& zcb) { 534 | assert(shm_.is_initialized()); 535 | uint64_t head_pos = shm_->head_pos; 536 | uint64_t tail_pos = shm_->tail_pos; 537 | uint64_t queue_size = shm_->queue_buf_size; 538 | 539 | assert((head_pos & (kAlignSize - 1)) == 0); 540 | assert((tail_pos & (kAlignSize - 1)) == 0); 541 | assert((queue_size & (kAlignSize - 1)) == 0); 542 | assert(head_pos < queue_size && tail_pos < queue_size); 543 | assert(zcb.ptr); 544 | 545 | uint64_t read_pos = static_cast(zcb.ptr) - sizeof(NodeHead) 546 | - shm_->queue_buf; 547 | if (head_pos == tail_pos) { 548 | Utils::Log(kWarning, "ShmQueue::ZeroCopyPopCommit: overflow\n"); 549 | return false; 550 | } 551 | if (read_pos == head_pos) { 552 | if (zcb.len != GetNodeHead(read_pos)->len) { 553 | Utils::Log(kWarning, "ShmQueue::ZeroCopyPopCommit: bad len\n"); 554 | return false; 555 | } 556 | } else if (read_pos == 0) { // && read_pos != head_pos 557 | volatile NodeHead* node_head = GetNodeHead(head_pos); 558 | if (node_head->start_tag != kStartTag || 559 | node_head->type != NodeType::kEndFlagNode) { 560 | Utils::Log(kWarning, "ShmQueue::ZeroCopyPopCommit: bad end-flag-node\n"); 561 | return false; 562 | } 563 | if (zcb.len != GetNodeHead(read_pos)->len) { 564 | Utils::Log(kWarning, "ShmQueue::ZeroCopyPopCommit: bad len\n"); 565 | return false; 566 | } 567 | } else { 568 | // invalid pos 569 | Utils::Log(kWarning, "ShmQueue::ZeroCopyPopCommit: invalid pos\n"); 570 | return false; 571 | } 572 | 573 | ZeroCopyPopCommitUnsafe(zcb); 574 | return true; 575 | } 576 | 577 | } // namespace shmc 578 | 579 | #endif // SHMC_SHM_QUEUE_H_ 580 | -------------------------------------------------------------------------------- /src/shmc/svipc_shm_alloc.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_SVIPC_SHM_ALLOC_H_ 31 | #define SHMC_SVIPC_SHM_ALLOC_H_ 32 | 33 | #include 34 | #include 35 | #include 36 | #include "shmc/shm_alloc.h" 37 | #include "shmc/common_utils.h" 38 | 39 | namespace shmc { 40 | 41 | namespace impl { 42 | 43 | template 44 | class SVIPCShmAlloc : public ShmAlloc { 45 | public: 46 | virtual ~SVIPCShmAlloc() {} 47 | 48 | void* Attach(const std::string& key, size_t size, int flags, 49 | size_t* mapped_size) override { 50 | key_t shm_key; 51 | if (!str2key(key, &shm_key)) { 52 | set_last_errno(kErrBadParam); 53 | return nullptr; 54 | } 55 | int shmget_flags = 0640; 56 | if (flags & kCreate) 57 | shmget_flags |= IPC_CREAT; 58 | if (flags & kCreateExcl) 59 | shmget_flags |= IPC_EXCL; 60 | if (kEnableHugeTLB) { 61 | shmget_flags |= SHM_HUGETLB; 62 | size = shmc::Utils::RoundAlign(size); 63 | } 64 | int shm_id = shmget(shm_key, size, shmget_flags); 65 | if (shm_id < 0) { 66 | // if got EINVAL when attaching existing shm 67 | if (errno == EINVAL && !(flags & kCreate)) { 68 | set_last_errno(kErrBiggerSize); 69 | } else { 70 | set_last_errno(conv_errno()); 71 | } 72 | return nullptr; 73 | } 74 | int shmat_flags = 0; 75 | if (flags & kReadOnly) 76 | shmat_flags |= SHM_RDONLY; 77 | void* addr = shmat(shm_id, nullptr, shmat_flags); 78 | if (addr == reinterpret_cast(-1)) { 79 | set_last_errno(conv_errno()); 80 | return nullptr; 81 | } 82 | if (mapped_size) { 83 | struct shmid_ds shmds; 84 | if (shmctl(shm_id, IPC_STAT, &shmds) < 0) { 85 | set_last_errno(conv_errno()); 86 | shmdt(addr); 87 | return nullptr; 88 | } 89 | *mapped_size = shmds.shm_segsz; 90 | } 91 | return addr; 92 | } 93 | 94 | bool Detach(void* addr, size_t size) override { 95 | if (shmdt(addr) < 0) { 96 | set_last_errno(conv_errno()); 97 | return false; 98 | } 99 | return true; 100 | } 101 | 102 | bool Unlink(const std::string& key) override { 103 | key_t shm_key; 104 | if (!str2key(key, &shm_key)) { 105 | set_last_errno(kErrBadParam); 106 | return false; 107 | } 108 | int shm_id = shmget(shm_key, 0, 0); 109 | if (shm_id < 0) { 110 | set_last_errno(conv_errno()); 111 | return false; 112 | } 113 | if (shmctl(shm_id, IPC_RMID, nullptr) < 0) { 114 | set_last_errno(conv_errno()); 115 | return false; 116 | } 117 | return true; 118 | } 119 | size_t AlignSize() override { 120 | return (kEnableHugeTLB ? kAlignSize_HugeTLB : 1); 121 | } 122 | 123 | private: 124 | // 2M-align is a workaround for kernel bug with HugeTLB 125 | // which has been fixed in new kernels 126 | // https://bugzilla.kernel.org/show_bug.cgi?id=56881 127 | static constexpr size_t kAlignSize_HugeTLB = 0x200000UL; 128 | 129 | static bool str2key(const std::string& key, key_t* out) { 130 | // support decimal, octal, hexadecimal format 131 | errno = 0; 132 | int64_t shm_key = strtol(key.c_str(), NULL, 0); 133 | if (errno) 134 | return false; 135 | *out = static_cast(shm_key); 136 | return true; 137 | } 138 | 139 | static ShmAllocErrno conv_errno() { 140 | switch (errno) { 141 | case 0: 142 | return kErrOK; 143 | case ENOENT: 144 | return kErrNotExist; 145 | case EEXIST: 146 | return kErrAlreadyExist; 147 | case EINVAL: 148 | return kErrInvalid; 149 | case EACCES: 150 | case EPERM: 151 | return kErrPermission; 152 | default: 153 | return kErrDefault; 154 | } 155 | } 156 | }; 157 | 158 | } // namespace impl 159 | 160 | using SVIPC_HugeTLB = impl::SVIPCShmAlloc; 161 | using SVIPC = impl::SVIPCShmAlloc; 162 | 163 | } // namespace shmc 164 | 165 | #endif // SHMC_SVIPC_SHM_ALLOC_H_ 166 | -------------------------------------------------------------------------------- /src/shmc/version.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SHMC_VERSION_H_ 31 | #define SHMC_VERSION_H_ 32 | 33 | // conform to Semantic Versioning (http://semver.org) 34 | 35 | #define SHMC_MAJOR_VERSION 1 36 | #define SHMC_MINOR_VERSION 0 37 | #define SHMC_PATCH_VERSION 0 38 | //#define SHMC_PRERLEASE_VERSION 39 | 40 | #endif // SHMC_VERSION_H_ 41 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | The tests have covered all shm types: POSIX, SVIPC, SVIPC_HugeTLB, among which SVIPC_HugeTLB need some special privileges. 2 | You should: 3 | 1) Set vm.nr_hugepages by sysctl 4 | 2) Set vm.hugetlb_shm_group with you group 5 | 6 | You can also skip tests for SVIPC_HugeTLB through command like this: 7 | bazel run test -- --gtest_filter='-*/2*' 8 | -------------------------------------------------------------------------------- /test/shm_alloc_test.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include "gtestx/gtestx.h" 31 | #include "shmc/svipc_shm_alloc.h" 32 | #include "shmc/posix_shm_alloc.h" 33 | 34 | namespace { 35 | 36 | using TestTypes = testing::Types; 37 | 38 | } // namespace 39 | 40 | template 41 | class ShmAllocTest : public testing::Test { 42 | protected: 43 | virtual ~ShmAllocTest() {} 44 | virtual void SetUp() { 45 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 46 | fprintf(stderr, "[%d] %s", lv, s); 47 | }); 48 | alloc_ = new T(); 49 | alloc_->Unlink("0x10001"); 50 | } 51 | virtual void TearDown() { 52 | alloc_->Unlink("0x10001"); 53 | delete alloc_; 54 | } 55 | bool CheckShmSize(size_t mapped_size, size_t expected) { 56 | return mapped_size == shmc::Utils::RoundAlign(expected, alloc_->AlignSize()); 57 | } 58 | shmc::impl::ShmAlloc* alloc_; 59 | }; 60 | TYPED_TEST_CASE(ShmAllocTest, TestTypes); 61 | 62 | TYPED_TEST(ShmAllocTest, CreateUnlink) { 63 | ASSERT_TRUE(this->alloc_->Attach_ExclCreate("0x10001", 10000000UL)); 64 | ASSERT_FALSE(this->alloc_->Attach_ExclCreate("0x10001", 10000000UL)); 65 | ASSERT_EQ(shmc::impl::kErrAlreadyExist, this->alloc_->last_errno()); 66 | ASSERT_TRUE(this->alloc_->Unlink("0x10001")); 67 | ASSERT_FALSE(this->alloc_->Unlink("0x10001")); 68 | ASSERT_EQ(shmc::impl::kErrNotExist, this->alloc_->last_errno()); 69 | ASSERT_TRUE(this->alloc_->Attach_ExclCreate("0x10001", 10000000UL)); 70 | ASSERT_TRUE(this->alloc_->Attach_ForceCreate("0x10001", 10000000UL)); 71 | ASSERT_TRUE(this->alloc_->Unlink("0x10001")); 72 | } 73 | 74 | TYPED_TEST(ShmAllocTest, AttachDetach) { 75 | void* addr; 76 | ASSERT_FALSE((addr = this->alloc_->Attach_ReadOnly("0x10001", 10000000UL))); 77 | ASSERT_TRUE((addr = this->alloc_->Attach_MayCreate("0x10001", 10000000UL))); 78 | ASSERT_EQ(0, *reinterpret_cast(static_cast(addr) + 10000000 - 4)); 79 | *reinterpret_cast(static_cast(addr) + 10000000 - 4) = 123456789; 80 | ASSERT_TRUE(this->alloc_->Detach(addr, 10000000UL)); 81 | ASSERT_TRUE((addr = this->alloc_->Attach_ReadOnly("0x10001", 10000000UL))); 82 | ASSERT_EQ(123456789, *reinterpret_cast(static_cast(addr) + 10000000 - 4)); 83 | ASSERT_TRUE(this->alloc_->Detach(addr, 10000000UL)); 84 | ASSERT_TRUE(this->alloc_->Unlink("0x10001")); 85 | } 86 | 87 | TYPED_TEST(ShmAllocTest, AttachSize) { 88 | void* addr; 89 | size_t mapped_size; 90 | bool created = false; 91 | ASSERT_TRUE((addr = this->alloc_->Attach_MayCreate("0x10001", 10000000UL, &mapped_size, &created))); 92 | ASSERT_TRUE(this->CheckShmSize(mapped_size, 10000000UL)); 93 | ASSERT_TRUE(created); 94 | ASSERT_TRUE(this->alloc_->Detach(addr, 10000000UL)); 95 | ASSERT_FALSE((addr = this->alloc_->Attach_MayCreate("0x10001", 15000000UL))); 96 | ASSERT_TRUE((addr = this->alloc_->Attach_MayCreate("0x10001", 0, &mapped_size, &created))); 97 | ASSERT_TRUE(this->CheckShmSize(mapped_size, 10000000UL)); 98 | ASSERT_FALSE(created); 99 | ASSERT_TRUE(this->alloc_->Detach(addr, 10000000UL)); 100 | ASSERT_FALSE((addr = this->alloc_->Attach_ReadOnly("0x10001", 15000000UL))); 101 | ASSERT_TRUE((addr = this->alloc_->Attach_ReadOnly("0x10001", 0, &mapped_size))); 102 | ASSERT_TRUE(this->CheckShmSize(mapped_size, 10000000UL)); 103 | ASSERT_TRUE(this->alloc_->Detach(addr, 10000000UL)); 104 | } 105 | 106 | TYPED_TEST(ShmAllocTest, AutoCreate) { 107 | void* addr; 108 | size_t mapped_size; 109 | bool created = false; 110 | ASSERT_FALSE((addr = this->alloc_->Attach_AutoCreate("0x10001", 10000000UL, shmc::kNoCreate, &mapped_size, &created))); 111 | ASSERT_TRUE((addr = this->alloc_->Attach_AutoCreate("0x10001", 10000000UL, shmc::kCreateIfNotExist, &mapped_size, &created))); 112 | ASSERT_TRUE(this->CheckShmSize(mapped_size, 10000000UL)); 113 | ASSERT_TRUE(created); 114 | ASSERT_TRUE(this->alloc_->Detach(addr, 10000000UL)); 115 | ASSERT_TRUE((addr = this->alloc_->Attach_AutoCreate("0x10001", 0, shmc::kNoCreate, &mapped_size, &created))); 116 | ASSERT_TRUE(this->CheckShmSize(mapped_size, 10000000UL)); 117 | ASSERT_FALSE(created); 118 | ASSERT_TRUE(this->alloc_->Detach(addr, 10000000UL)); 119 | ASSERT_FALSE((addr = this->alloc_->Attach_AutoCreate("0x10001", 11000000UL, shmc::kCreateIfNotExist, &mapped_size, &created))); 120 | ASSERT_TRUE((addr = this->alloc_->Attach_AutoCreate("0x10001", 11000000UL, shmc::kCreateIfExtending, &mapped_size, &created))); 121 | ASSERT_TRUE(this->CheckShmSize(mapped_size, 11000000UL)); 122 | ASSERT_TRUE(created); 123 | ASSERT_TRUE(this->alloc_->Detach(addr, 11000000UL)); 124 | } 125 | -------------------------------------------------------------------------------- /test/shm_array_test.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include 31 | #include "gtestx/gtestx.h" 32 | #include "shmc/shm_array.h" 33 | 34 | namespace { 35 | 36 | constexpr const char* kShmKey = "0x10006"; 37 | constexpr size_t kSize = 1000000; 38 | 39 | using TestTypes = testing::Types; 41 | 42 | struct Node { 43 | uint32_t id; 44 | uint32_t val; 45 | } __attribute__((packed)); 46 | 47 | } // namespace 48 | 49 | template 50 | class ShmArrayTest : public testing::Test { 51 | protected: 52 | virtual ~ShmArrayTest() {} 53 | virtual void SetUp() { 54 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 55 | fprintf(stderr, "[%d] %s", lv, s); 56 | }); 57 | this->alloc_.Unlink(kShmKey); 58 | shmc::Utils::SetDefaultCreateFlags(shmc::kCreateIfNotExist); 59 | ASSERT_TRUE(this->array_.InitForWrite(kShmKey, kSize)); 60 | if (shmc::impl::AllocTraits::is_named) { 61 | this->array_ro_ = &this->array2_; 62 | ASSERT_TRUE(this->array_ro_->InitForRead(kShmKey)); 63 | } else { 64 | this->array_ro_ = &this->array_; 65 | } 66 | } 67 | virtual void TearDown() { 68 | this->alloc_.Unlink(kShmKey); 69 | shmc::Utils::SetDefaultCreateFlags(shmc::kCreateIfNotExist); 70 | } 71 | shmc::ShmArray array_; 72 | shmc::ShmArray array2_; 73 | shmc::ShmArray* array_ro_; 74 | Alloc alloc_; 75 | }; 76 | TYPED_TEST_CASE(ShmArrayTest, TestTypes); 77 | 78 | TYPED_TEST(ShmArrayTest, InitAndRW) { 79 | for (size_t i = 0; i < kSize; i++) { 80 | this->array_[i].id = i; 81 | } 82 | const shmc::ShmArray& array_ro = *this->array_ro_; 83 | for (size_t i = 0; i < kSize; i++) { 84 | auto id = array_ro[i].id; 85 | ASSERT_EQ(i, id); 86 | } 87 | } 88 | 89 | TYPED_TEST(ShmArrayTest, CreateFlags) { 90 | if (shmc::impl::AllocTraits::is_named) { 91 | shmc::ShmArray array_new; 92 | ASSERT_FALSE(array_new.InitForWrite(kShmKey, kSize*2)); 93 | shmc::Utils::SetDefaultCreateFlags(shmc::kCreateIfExtending); 94 | ASSERT_TRUE(array_new.InitForWrite(kShmKey, kSize*2)); 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /test/shm_handle_for_heap_test.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include "gtestx/gtestx.h" 31 | #include "shmc/shm_handle.h" 32 | 33 | namespace { 34 | 35 | using TestTypes = testing::Types; 36 | 37 | } // namespace 38 | 39 | template 40 | class ShmHandleForHeapTest : public testing::Test { 41 | protected: 42 | virtual ~ShmHandleForHeapTest() {} 43 | virtual void SetUp() { 44 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 45 | fprintf(stderr, "[%d] %s", lv, s); 46 | }); 47 | Alloc().Unlink(""); 48 | } 49 | virtual void TearDown() { 50 | Alloc().Unlink(""); 51 | } 52 | shmc::ShmHandle shm_handle_; 53 | }; 54 | TYPED_TEST_CASE(ShmHandleForHeapTest, TestTypes); 55 | 56 | TYPED_TEST(ShmHandleForHeapTest, InitForWrite) { 57 | ASSERT_TRUE(this->shm_handle_.InitForWrite("", 10000000)); 58 | ASSERT_TRUE(this->shm_handle_.is_newly_created()); 59 | ASSERT_EQ(this->shm_handle_.ptr()[10000000-1], 0); 60 | this->shm_handle_.ptr()[10000000-1] = 1; 61 | ASSERT_FALSE(this->shm_handle_.InitForWrite("", 10000000)); 62 | this->shm_handle_.Reset(); 63 | ASSERT_TRUE(this->shm_handle_.InitForWrite("", 10000000)); 64 | ASSERT_TRUE(this->shm_handle_.is_newly_created()); 65 | this->shm_handle_.ptr()[10000000-1]++; 66 | ASSERT_EQ(this->shm_handle_.ptr()[10000000-1], 1); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /test/shm_handle_test.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include "gtestx/gtestx.h" 31 | #include "shmc/shm_handle.h" 32 | #include "shmc/svipc_shm_alloc.h" 33 | 34 | namespace { 35 | 36 | using TestTypes = testing::Types; 37 | 38 | } // namespace 39 | 40 | template 41 | class ShmHandleTest : public testing::Test { 42 | protected: 43 | virtual ~ShmHandleTest() {} 44 | virtual void SetUp() { 45 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 46 | fprintf(stderr, "[%d] %s", lv, s); 47 | }); 48 | Alloc().Unlink("0x10002"); 49 | } 50 | virtual void TearDown() { 51 | Alloc().Unlink("0x10002"); 52 | } 53 | shmc::ShmHandle shm_handle_; 54 | }; 55 | TYPED_TEST_CASE(ShmHandleTest, TestTypes); 56 | 57 | TYPED_TEST(ShmHandleTest, InitForWrite) { 58 | ASSERT_TRUE(this->shm_handle_.InitForWrite("0x10002", 10000000)); 59 | ASSERT_TRUE(this->shm_handle_.is_newly_created()); 60 | ASSERT_EQ(this->shm_handle_.ptr()[10000000-1], 0); 61 | this->shm_handle_.ptr()[10000000-1] = 1; 62 | ASSERT_FALSE(this->shm_handle_.InitForWrite("0x10002", 10000000)); 63 | this->shm_handle_.Reset(); 64 | ASSERT_TRUE(this->shm_handle_.InitForWrite("0x10002", 10000000)); 65 | ASSERT_FALSE(this->shm_handle_.is_newly_created()); 66 | this->shm_handle_.ptr()[10000000-1]++; 67 | ASSERT_EQ(this->shm_handle_.ptr()[10000000-1], 2); 68 | } 69 | 70 | TYPED_TEST(ShmHandleTest, InitForRead) { 71 | ASSERT_FALSE(this->shm_handle_.InitForRead("0x10002", 10000000)); 72 | ASSERT_TRUE(this->shm_handle_.InitForWrite("0x10002", 10000000)); 73 | ASSERT_TRUE(this->shm_handle_.is_newly_created()); 74 | ASSERT_EQ(this->shm_handle_.ptr()[10000000-1], 0); 75 | this->shm_handle_.ptr()[10000000-1] = 1; 76 | this->shm_handle_.Reset(); 77 | ASSERT_TRUE(this->shm_handle_.InitForRead("0x10002", 10000000)); 78 | ASSERT_EQ(this->shm_handle_.ptr()[10000000-1], 1); 79 | ASSERT_FALSE(this->shm_handle_.InitForRead("0x10002", 10000000)); 80 | } 81 | -------------------------------------------------------------------------------- /test/shm_hash_map_test.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include 31 | #include 32 | #include "gtestx/gtestx.h" 33 | #include "shmc/shm_hash_map.h" 34 | 35 | namespace { 36 | 37 | constexpr const char* kShmKey = "0x10005"; 38 | constexpr size_t kKeyNum = 300000; 39 | constexpr size_t kNodeSize = 32; 40 | constexpr size_t kNodeNum = 2000000; 41 | 42 | using TestTypes = testing::Types; 44 | 45 | } // namespace 46 | 47 | template 48 | class ShmHashMapTest : public testing::Test { 49 | protected: 50 | virtual ~ShmHashMapTest() {} 51 | virtual void SetUp() { 52 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 53 | fprintf(stderr, "[%d] %s", lv, s); 54 | }); 55 | this->alloc_.Unlink(kShmKey + std::string("0")); 56 | this->alloc_.Unlink(kShmKey + std::string("1")); 57 | } 58 | virtual void TearDown() { 59 | this->alloc_.Unlink(kShmKey + std::string("0")); 60 | this->alloc_.Unlink(kShmKey + std::string("1")); 61 | } 62 | shmc::ShmHashMap hash_map_; 63 | Alloc alloc_; 64 | 65 | decltype(hash_map_.hash_table_)* hash_table() { 66 | return &hash_map_.hash_table_; 67 | } 68 | decltype(hash_map_.link_table_)* link_table() { 69 | return &hash_map_.link_table_; 70 | } 71 | }; 72 | TYPED_TEST_CASE(ShmHashMapTest, TestTypes); 73 | 74 | TYPED_TEST(ShmHashMapTest, ReadWrite) { 75 | ASSERT_TRUE(this->hash_map_.InitForWrite(kShmKey, kKeyNum, kNodeSize, kNodeNum)); 76 | ASSERT_TRUE(this->hash_map_.Insert(10000, "hello")); 77 | std::string out; 78 | ASSERT_TRUE(this->hash_map_.Find(10000, &out)); 79 | ASSERT_EQ("hello", out); 80 | ASSERT_FALSE(this->hash_map_.Insert(10000, "world")); 81 | ASSERT_TRUE(this->hash_map_.Replace(10000, "world")); 82 | ASSERT_TRUE(this->hash_map_.Find(10000, &out)); 83 | ASSERT_EQ("world", out); 84 | ASSERT_TRUE(this->hash_map_.Erase(10000)); 85 | ASSERT_FALSE(this->hash_map_.Find(10000, &out)); 86 | } 87 | 88 | TYPED_TEST(ShmHashMapTest, ReadWriteBig) { 89 | ASSERT_TRUE(this->hash_map_.InitForWrite(kShmKey, kKeyNum, kNodeSize, kNodeNum)); 90 | int buf[1000]; 91 | for (int i = 0; i < 1000; i++) buf[i] = i; 92 | ASSERT_TRUE(this->hash_map_.Insert(1000, buf, sizeof(buf))); 93 | std::string out; 94 | ASSERT_TRUE(this->hash_map_.Find(1000, &out)); 95 | ASSERT_EQ(sizeof(buf), out.size()); 96 | const int* out_buf = reinterpret_cast(out.data()); 97 | for (int i = 0; i < 1000; i++) { 98 | ASSERT_EQ(i, out_buf[i]); 99 | } 100 | ASSERT_TRUE(this->hash_map_.Insert(1001, buf, sizeof(buf))); 101 | ASSERT_TRUE(this->hash_map_.Find(1000, &out)); 102 | ASSERT_EQ(sizeof(buf), out.size()); 103 | out_buf = reinterpret_cast(out.data()); 104 | for (int i = 0; i < 1000; i++) { 105 | ASSERT_EQ(i, out_buf[i]); 106 | } 107 | ASSERT_TRUE(this->hash_map_.Erase(1000)); 108 | ASSERT_TRUE(this->hash_map_.Erase(1001)); 109 | } 110 | 111 | TYPED_TEST(ShmHashMapTest, Travel) { 112 | ASSERT_TRUE(this->hash_map_.InitForWrite(kShmKey, kKeyNum, kNodeSize, kNodeNum)); 113 | for (size_t i = 0; i < 10; i++) { 114 | char buf[20]; 115 | snprintf(buf, sizeof(buf), "%lu", i); 116 | ASSERT_TRUE(this->hash_map_.Insert(i, buf, sizeof(buf))); 117 | } 118 | // travel all 119 | size_t sum = 0; 120 | ASSERT_TRUE(this->hash_map_.Travel([&sum](const size_t& key, const std::string& val) { 121 | size_t val_n = strtoul(val.c_str(), NULL, 0); 122 | ASSERT_EQ(val_n, key); 123 | sum += key; 124 | })); 125 | ASSERT_EQ(45UL, sum); 126 | // travel with step 1 127 | sum = 0; 128 | typename decltype(this->hash_map_)::TravelPos pos; 129 | do { 130 | ASSERT_TRUE(this->hash_map_.Travel(&pos, 1, [&sum](const size_t& key, const std::string& val) { 131 | size_t val_n = strtoul(val.c_str(), NULL, 0); 132 | ASSERT_EQ(val_n, key); 133 | sum += key; 134 | })); 135 | } while (!pos.at_origin()); 136 | ASSERT_EQ(45UL, sum); 137 | } 138 | 139 | TYPED_TEST(ShmHashMapTest, HealthCheck) { 140 | ASSERT_TRUE(this->hash_map_.InitForWrite(kShmKey, kKeyNum, kNodeSize, kNodeNum)); 141 | ASSERT_EQ(100UL, this->hash_map_.free_percentage()); 142 | typename decltype(this->hash_map_)::HealthStat hstat; 143 | ASSERT_TRUE(this->hash_map_.HealthCheck(&hstat, true)); 144 | EXPECT_EQ(0UL, hstat.total_key_values); 145 | EXPECT_EQ(0UL, hstat.bad_key_values); 146 | EXPECT_EQ(0UL, hstat.cleared_key_values); 147 | EXPECT_EQ(0UL, hstat.leaked_values); 148 | EXPECT_EQ(0UL, hstat.recycled_leaked_values); 149 | // bad key value 150 | bool is_found; 151 | auto node = this->hash_table()->FindOrAlloc(1, &is_found); 152 | ASSERT_TRUE(node && !is_found); 153 | node->key = 1; 154 | node->link_buf.m.head = 1; 155 | ASSERT_TRUE(this->hash_map_.HealthCheck(&hstat, true)); 156 | EXPECT_EQ(0UL, hstat.total_key_values); 157 | EXPECT_EQ(1UL, hstat.bad_key_values); 158 | EXPECT_EQ(1UL, hstat.cleared_key_values); 159 | EXPECT_EQ(0UL, hstat.leaked_values); 160 | EXPECT_EQ(0UL, hstat.recycled_leaked_values); 161 | // leaked value 162 | char buf[40]; 163 | auto lb = this->link_table()->New(buf, sizeof(buf)); 164 | ASSERT_TRUE(lb); 165 | ASSERT_TRUE(this->hash_map_.HealthCheck(&hstat, true)); 166 | EXPECT_EQ(0UL, hstat.total_key_values); 167 | EXPECT_EQ(0UL, hstat.bad_key_values); 168 | EXPECT_EQ(0UL, hstat.cleared_key_values); 169 | EXPECT_EQ(1UL, hstat.leaked_values); 170 | EXPECT_EQ(1UL, hstat.recycled_leaked_values); 171 | // check again 172 | ASSERT_TRUE(this->hash_map_.HealthCheck(&hstat, true)); 173 | EXPECT_EQ(0UL, hstat.total_key_values); 174 | EXPECT_EQ(0UL, hstat.bad_key_values); 175 | EXPECT_EQ(0UL, hstat.cleared_key_values); 176 | EXPECT_EQ(0UL, hstat.leaked_values); 177 | EXPECT_EQ(0UL, hstat.recycled_leaked_values); 178 | } 179 | 180 | template 181 | class ShmHashMapPerfTest : public ShmHashMapTest { 182 | protected: 183 | virtual ~ShmHashMapPerfTest() {} 184 | virtual void SetUp() { 185 | ShmHashMapTest::SetUp(); 186 | this->hash_map_.InitForWrite(kShmKey, kKeyNum, kNodeSize, kNodeNum); 187 | char buf[200]; 188 | for (size_t i = 0; ; i++) { 189 | for (size_t j = 0; j < sizeof(buf); j++) buf[j] = (char)i; 190 | if (!this->hash_map_.Insert(i, buf, sizeof(buf))) { 191 | std::cerr << "Insert done at i: " << i << std::endl; 192 | break;; 193 | } 194 | } 195 | } 196 | virtual void TearDown() { 197 | ShmHashMapTest::TearDown(); 198 | } 199 | }; 200 | TYPED_TEST_CASE(ShmHashMapPerfTest, TestTypes); 201 | 202 | TYPED_PERF_TEST(ShmHashMapPerfTest, PerfRead_100B) { 203 | std::string out; 204 | ASSERT_TRUE(this->hash_map_.Find(200000, &out)) << PERF_ABORT; 205 | } 206 | 207 | namespace { 208 | 209 | constexpr size_t kTestKeyMax = 100; 210 | 211 | using PerfTestCTypes = testing::Types; 213 | 214 | } // namespace 215 | 216 | template 217 | class ShmHashMapPerfTestC : public ShmHashMapPerfTest { 218 | protected: 219 | virtual ~ShmHashMapPerfTestC() {} 220 | virtual void SetUp() { 221 | ShmHashMapPerfTest::SetUp(); 222 | if (!(wpid_ = fork())) { 223 | WriteProcess(); 224 | exit(0); 225 | } 226 | } 227 | virtual void TearDown() { 228 | kill(wpid_, SIGTERM); 229 | waitpid(wpid_, nullptr, 0); 230 | ShmHashMapPerfTest::TearDown(); 231 | } 232 | void WriteProcess() { 233 | srand(time(NULL)); 234 | this->hash_map_.Erase(kTestKeyMax); 235 | char buf[200]; 236 | while (true) { 237 | size_t k = rand() % kTestKeyMax; 238 | size_t n = rand() % sizeof(buf) + 1; 239 | for (size_t i = 0; i < sizeof(buf); i++) buf[i] = (char)k; 240 | if (!this->hash_map_.Replace(k, (void*)buf, n)) { 241 | std::cerr << "Replace fail! key=" << k << " n=" << n << std::endl; 242 | } 243 | } 244 | } 245 | pid_t wpid_; 246 | }; 247 | TYPED_TEST_CASE(ShmHashMapPerfTestC, PerfTestCTypes); 248 | 249 | TYPED_PERF_TEST(ShmHashMapPerfTestC, PerfRead_Concurrent) { 250 | static size_t key = 0; 251 | std::string out; 252 | ASSERT_TRUE(this->hash_map_.Find(key, &out)) << "key=" << key << PERF_ABORT; 253 | for (size_t i = 0; i < out.size(); i++) { 254 | if ((char)key != out[i]) { 255 | this->hash_map_.Dump(key); 256 | shmc::Utils::Log(shmc::kInfo, "dump out:\n%s", shmc::Utils::Hex(out.data(), out.size())); 257 | } 258 | ASSERT_EQ((char)key, out[i]) << "i = " << i << " pid=" << getpid() << PERF_ABORT; 259 | } 260 | key = (key + 1) % kTestKeyMax; 261 | } 262 | 263 | -------------------------------------------------------------------------------- /test/shm_hash_table_m_test.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include 31 | #include "gtestx/gtestx.h" 32 | #include "shmc/shm_hash_table_m.h" 33 | 34 | namespace { 35 | 36 | constexpr const char* kShmKey = "0x10003"; 37 | constexpr size_t kColNum = 50000; 38 | constexpr size_t kRowNum = 50; 39 | 40 | using TestTypes = testing::Types; 42 | 43 | struct Node { 44 | uint64_t key; 45 | uint64_t value; 46 | std::pair Key() const volatile { 47 | return std::make_pair((key != 0), key); 48 | } 49 | }; 50 | 51 | } // namespace 52 | 53 | template 54 | class ShmHashTableMTest : public testing::Test { 55 | protected: 56 | virtual ~ShmHashTableMTest() {} 57 | virtual void SetUp() { 58 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 59 | fprintf(stderr, "[%d] %s", lv, s); 60 | }); 61 | this->alloc_.Unlink(kShmKey); 62 | } 63 | virtual void TearDown() { 64 | this->alloc_.Unlink(kShmKey); 65 | } 66 | shmc::ShmHashTableM hash_tab_; 67 | Alloc alloc_; 68 | }; 69 | TYPED_TEST_CASE(ShmHashTableMTest, TestTypes); 70 | 71 | TYPED_TEST(ShmHashTableMTest, Write) { 72 | ASSERT_TRUE(this->hash_tab_.InitForWrite(kShmKey, kColNum, kRowNum)); 73 | ASSERT_LT(this->hash_tab_.ideal_capacity(), kColNum * kRowNum); 74 | ASSERT_GT(this->hash_tab_.ideal_capacity(), kColNum * kRowNum * 95 / 100); 75 | srand(time(NULL)); 76 | ASSERT_FALSE(this->hash_tab_.Find(1)); 77 | size_t count = 0; 78 | while (true) { 79 | bool is_found; 80 | uint64_t k = rand(); 81 | volatile Node* node = this->hash_tab_.FindOrAlloc(k, &is_found); 82 | if (!node) { 83 | size_t perc = count * 100 / this->hash_tab_.ideal_capacity(); 84 | fprintf(stderr, "Almost full: count = %lu [%lu%%]\n", count, perc); 85 | ASSERT_GT(perc, 90UL) << "Not reach target of 90%!"; 86 | ASSERT_GT(count, this->hash_tab_.expected_capacity()) << "Not reach expected capacity!"; 87 | break; 88 | } else if (!is_found) { 89 | node->key = k; 90 | node->value = k; 91 | count++; 92 | } 93 | } 94 | } 95 | 96 | TYPED_TEST(ShmHashTableMTest, Travel) { 97 | ASSERT_TRUE(this->hash_tab_.InitForWrite(kShmKey, kColNum, kRowNum)); 98 | for (uint64_t k = 1; k <= 10; k++) { 99 | bool is_found; 100 | volatile Node* node = this->hash_tab_.FindOrAlloc(k, &is_found); 101 | ASSERT_TRUE(node && !is_found); 102 | node->key = k; 103 | node->value = k; 104 | } 105 | // travel all 106 | uint64_t sum = 0; 107 | ASSERT_TRUE(this->hash_tab_.Travel([&sum](volatile Node* node) { 108 | ASSERT_EQ(node->value, node->key); 109 | sum += node->key; 110 | })); 111 | ASSERT_EQ(55UL, sum); 112 | // travel with step 1 113 | typename decltype(this->hash_tab_)::TravelPos pos; 114 | sum = 0; 115 | do { 116 | ASSERT_TRUE(this->hash_tab_.Travel(&pos, 1, [&sum](volatile Node* node) { 117 | ASSERT_EQ(node->value, node->key); 118 | sum += node->key; 119 | })); 120 | } while (!pos.at_origin()); 121 | ASSERT_EQ(55UL, sum); 122 | } 123 | 124 | namespace { 125 | 126 | using ReadTestTypes = testing::Types; 128 | 129 | } // namespace 130 | 131 | template 132 | class ShmHashTableMReadTest : public ShmHashTableMTest { 133 | protected: 134 | virtual ~ShmHashTableMReadTest() {} 135 | virtual void SetUp() { 136 | ShmHashTableMTest::SetUp(); 137 | shmc::ShmHashTableM hash_tab; 138 | hash_tab.InitForWrite(kShmKey, kColNum, kRowNum); 139 | srand(time(NULL)); 140 | size_t sample = hash_tab.expected_capacity() / 256; 141 | size_t count = 0; 142 | while (true) { 143 | bool is_found; 144 | uint64_t k = rand(); 145 | volatile Node* node = hash_tab.FindOrAlloc(k, &is_found); 146 | if (!node) { 147 | size_t perc = count * 100 / hash_tab.ideal_capacity(); 148 | fprintf(stderr, "Almost full: count = %lu [%lu%%]\n", count, perc); 149 | break; 150 | } else if (!is_found) { 151 | node->key = k; 152 | node->value = k; 153 | count++; 154 | if (count % sample == 0) keys_.push_back(k); 155 | } 156 | } 157 | } 158 | virtual void TearDown() { 159 | ShmHashTableMTest::TearDown(); 160 | } 161 | std::vector keys_; 162 | }; 163 | TYPED_TEST_CASE(ShmHashTableMReadTest, ReadTestTypes); 164 | 165 | TYPED_TEST(ShmHashTableMReadTest, Read) { 166 | ASSERT_TRUE(this->hash_tab_.InitForRead(kShmKey)); 167 | ASSERT_LT(this->hash_tab_.ideal_capacity(), kColNum * kRowNum); 168 | ASSERT_GT(this->hash_tab_.ideal_capacity(), kColNum * kRowNum * 95 / 100); 169 | for (uint64_t k : this->keys_) { 170 | volatile Node* node = this->hash_tab_.Find(k); 171 | ASSERT_TRUE(node); 172 | ASSERT_EQ(node->value, node->key); 173 | } 174 | } 175 | 176 | TYPED_PERF_TEST(ShmHashTableMReadTest, PerfRead) { 177 | static bool is_inited = false; 178 | static uint64_t k = 0; 179 | if (!is_inited) { 180 | ASSERT_TRUE(this->hash_tab_.InitForRead(kShmKey)) << PERF_ABORT; 181 | is_inited = true; 182 | } 183 | this->hash_tab_.Find(k++); 184 | } 185 | 186 | TYPED_PERF_TEST(ShmHashTableMReadTest, PerfReadExist) { 187 | static bool is_inited = false; 188 | static uint64_t k = 0; 189 | if (!is_inited) { 190 | ASSERT_TRUE(this->hash_tab_.InitForRead(kShmKey)) << PERF_ABORT; 191 | is_inited = true; 192 | } 193 | this->hash_tab_.Find(this->keys_[k++ % 256]); 194 | } 195 | -------------------------------------------------------------------------------- /test/shm_hash_table_test.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include 31 | #include "gtestx/gtestx.h" 32 | #include "shmc/shm_hash_table.h" 33 | 34 | namespace { 35 | 36 | constexpr const char* kShmKey = "0x10003"; 37 | constexpr size_t kCapacity = 2500000; 38 | 39 | using TestTypes = testing::Types; 41 | 42 | struct Node { 43 | uint64_t key; 44 | uint64_t value; 45 | std::pair Key() const volatile { 46 | return std::make_pair((key != 0), key); 47 | } 48 | std::pair Key(uint64_t arg) const volatile { 49 | return std::make_pair((key > arg), key); 50 | } 51 | }; 52 | 53 | } // namespace 54 | 55 | template 56 | class ShmHashTableTest : public testing::Test { 57 | protected: 58 | virtual ~ShmHashTableTest() {} 59 | virtual void SetUp() { 60 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 61 | fprintf(stderr, "[%d] %s", lv, s); 62 | }); 63 | this->alloc_.Unlink(kShmKey); 64 | } 65 | virtual void TearDown() { 66 | this->alloc_.Unlink(kShmKey); 67 | } 68 | shmc::ShmHashTable hash_tab_; 69 | Alloc alloc_; 70 | }; 71 | TYPED_TEST_CASE(ShmHashTableTest, TestTypes); 72 | 73 | TYPED_TEST(ShmHashTableTest, Write) { 74 | ASSERT_TRUE(this->hash_tab_.InitForWrite(kShmKey, kCapacity)); 75 | ASSERT_GT(this->hash_tab_.ideal_capacity(), kCapacity); 76 | ASSERT_GT(this->hash_tab_.expected_capacity(), kCapacity); 77 | srand(time(NULL)); 78 | ASSERT_FALSE(this->hash_tab_.Find(1)); 79 | size_t count = 0; 80 | while (true) { 81 | bool is_found; 82 | uint64_t k = rand(); 83 | volatile Node* node = this->hash_tab_.FindOrAlloc(k, &is_found); 84 | if (!node) { 85 | size_t perc = count * 100 / this->hash_tab_.ideal_capacity(); 86 | fprintf(stderr, "Almost full: count = %lu [%lu%%]\n", count, perc); 87 | ASSERT_GT(perc, 85UL) << "Not reach target of 85%!"; 88 | ASSERT_GT(count, this->hash_tab_.expected_capacity()) << "Not reach expected capacity!"; 89 | break; 90 | } else if (!is_found) { 91 | node->key = k; 92 | node->value = k; 93 | count++; 94 | } 95 | } 96 | } 97 | 98 | TYPED_TEST(ShmHashTableTest, ArgFind) { 99 | ASSERT_TRUE(this->hash_tab_.InitForWrite(kShmKey, kCapacity)); 100 | ASSERT_FALSE(this->hash_tab_.Find(1)); 101 | bool is_found; 102 | volatile Node* node = this->hash_tab_.FindOrAlloc(1, &is_found); 103 | ASSERT_TRUE(node && !is_found); 104 | node->key = 1; 105 | node->value = 1; 106 | ASSERT_TRUE(this->hash_tab_.Find(1)); 107 | ASSERT_TRUE(this->hash_tab_.Find(1, 0UL)); 108 | ASSERT_FALSE(this->hash_tab_.Find(1, 1UL)); 109 | ASSERT_TRUE(this->hash_tab_.FindOrAlloc(1, 1UL, &is_found)); 110 | ASSERT_FALSE(is_found); 111 | } 112 | 113 | TYPED_TEST(ShmHashTableTest, Travel) { 114 | ASSERT_TRUE(this->hash_tab_.InitForWrite(kShmKey, kCapacity)); 115 | for (uint64_t k = 1; k <= 10; k++) { 116 | bool is_found; 117 | volatile Node* node = this->hash_tab_.FindOrAlloc(k, &is_found); 118 | ASSERT_TRUE(node && !is_found); 119 | node->key = k; 120 | node->value = k; 121 | } 122 | // travel all 123 | uint64_t sum = 0; 124 | ASSERT_TRUE(this->hash_tab_.Travel([&sum](volatile Node* node) { 125 | ASSERT_EQ(node->value, node->key); 126 | sum += node->key; 127 | })); 128 | ASSERT_EQ(55UL, sum); 129 | // travel with step 1 130 | typename decltype(this->hash_tab_)::TravelPos pos; 131 | sum = 0; 132 | do { 133 | ASSERT_TRUE(this->hash_tab_.Travel(&pos, 1, [&sum](volatile Node* node) { 134 | ASSERT_EQ(node->value, node->key); 135 | sum += node->key; 136 | })); 137 | } while (!pos.at_origin()); 138 | ASSERT_EQ(55UL, sum); 139 | } 140 | 141 | template 142 | class ShmHashTableReadTest : public ShmHashTableTest { 143 | protected: 144 | virtual ~ShmHashTableReadTest() {} 145 | virtual void SetUp() { 146 | ShmHashTableTest::SetUp(); 147 | shmc::ShmHashTable& hash_tab = this->hash_tab_; 148 | hash_tab.InitForWrite(kShmKey, kCapacity); 149 | srand(time(NULL)); 150 | size_t sample = hash_tab.expected_capacity() / 8192; 151 | size_t count = 0; 152 | while (true) { 153 | bool is_found; 154 | uint64_t k = rand(); 155 | volatile Node* node = hash_tab.FindOrAlloc(k, &is_found); 156 | if (!node) { 157 | size_t perc = count * 100 / hash_tab.ideal_capacity(); 158 | fprintf(stderr, "Almost full: count = %lu [%lu%%]\n", count, perc); 159 | break; 160 | } else if (!is_found) { 161 | node->key = k; 162 | node->value = k; 163 | count++; 164 | if (count % sample == 0) keys_.push_back(k); 165 | } 166 | } 167 | } 168 | virtual void TearDown() { 169 | ShmHashTableTest::TearDown(); 170 | } 171 | std::vector keys_; 172 | }; 173 | TYPED_TEST_CASE(ShmHashTableReadTest, TestTypes); 174 | 175 | TYPED_TEST(ShmHashTableReadTest, Read) { 176 | ASSERT_GT(this->hash_tab_.ideal_capacity(), kCapacity); 177 | ASSERT_GT(this->hash_tab_.expected_capacity(), kCapacity); 178 | for (uint64_t k : this->keys_) { 179 | volatile Node* node = this->hash_tab_.Find(k); 180 | ASSERT_TRUE(node); 181 | ASSERT_EQ(node->value, node->key); 182 | } 183 | } 184 | 185 | TYPED_PERF_TEST(ShmHashTableReadTest, PerfRead) { 186 | static uint64_t k = 0; 187 | this->hash_tab_.Find(k++); 188 | } 189 | 190 | TYPED_PERF_TEST(ShmHashTableReadTest, PerfReadExist) { 191 | static uint64_t k = 0; 192 | this->hash_tab_.Find(this->keys_[k++ % 8192]); 193 | } 194 | -------------------------------------------------------------------------------- /test/shm_link_table_test.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include 31 | #include "gtestx/gtestx.h" 32 | #include "shmc/shm_link_table.h" 33 | 34 | namespace { 35 | 36 | constexpr const char* kShmKey = "0x10004"; 37 | constexpr size_t kNodeSize = 32; 38 | constexpr size_t kNodeNum = 1000000; 39 | 40 | using TestTypes = testing::Types; 42 | 43 | } // namespace 44 | 45 | template 46 | class ShmLinkTableTest : public testing::Test { 47 | protected: 48 | virtual ~ShmLinkTableTest() {} 49 | virtual void SetUp() { 50 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 51 | fprintf(stderr, "[%d] %s", lv, s); 52 | }); 53 | this->alloc_.Unlink(kShmKey); 54 | } 55 | virtual void TearDown() { 56 | this->alloc_.Unlink(kShmKey); 57 | } 58 | uint32_t InvokeAllocNode() { 59 | return link_tab_.AllocNode(); 60 | } 61 | void InvokeFreeNode(uint32_t n) { 62 | return link_tab_.FreeNode(n); 63 | } 64 | volatile typename shmc::ShmLinkTable::NodeHead* InvokeGetNode(size_t index) { 65 | return link_tab_.GetNode(index); 66 | } 67 | 68 | shmc::ShmLinkTable link_tab_; 69 | Alloc alloc_; 70 | }; 71 | TYPED_TEST_CASE(ShmLinkTableTest, TestTypes); 72 | 73 | TYPED_TEST(ShmLinkTableTest, Alloc) { 74 | ASSERT_TRUE(this->link_tab_.InitForWrite(kShmKey, kNodeSize, kNodeNum)); 75 | for (size_t i = 0; i < kNodeNum; i++) { 76 | ASSERT_TRUE(this->InvokeAllocNode()); 77 | ASSERT_EQ(kNodeNum - i - 1, this->link_tab_.free_nodes()); 78 | } 79 | ASSERT_FALSE(this->InvokeAllocNode()); 80 | for (size_t i = 0; i < 100; i++) { 81 | this->InvokeFreeNode(1); 82 | ASSERT_EQ(1UL, this->link_tab_.free_nodes()); 83 | ASSERT_TRUE(this->InvokeAllocNode()); 84 | } 85 | for (size_t i = 1; i <= kNodeNum; i++) { 86 | this->InvokeFreeNode(i); 87 | ASSERT_EQ(i, this->link_tab_.free_nodes()); 88 | } 89 | } 90 | 91 | TYPED_TEST(ShmLinkTableTest, Write) { 92 | ASSERT_TRUE(this->link_tab_.InitForWrite(kShmKey, kNodeSize, kNodeNum)); 93 | shmc::link_buf_t lb; 94 | ASSERT_TRUE((lb = this->link_tab_.New("hello", 6))); 95 | char read_buf[64]; 96 | size_t buf_len = sizeof(read_buf); 97 | ASSERT_TRUE(this->link_tab_.Read(lb, (void*)read_buf, &buf_len)); 98 | ASSERT_EQ(read_buf, std::string("hello")); 99 | ASSERT_TRUE(this->link_tab_.Free(lb)); 100 | } 101 | 102 | TYPED_TEST(ShmLinkTableTest, WriteBig) { 103 | ASSERT_TRUE(this->link_tab_.InitForWrite(kShmKey, kNodeSize, kNodeNum)); 104 | shmc::link_buf_t lb; 105 | char write_buf[1000]; 106 | for (size_t i = 0; i < sizeof(write_buf); i++) { 107 | write_buf[i] = (char)i; 108 | } 109 | ASSERT_TRUE((lb = this->link_tab_.New(write_buf, sizeof(write_buf)))); 110 | char read_buf[1000]; 111 | size_t buf_len = sizeof(read_buf); 112 | ASSERT_TRUE(this->link_tab_.Read(lb, (void*)read_buf, &buf_len)); 113 | ASSERT_EQ(sizeof(read_buf), buf_len); 114 | for (size_t i = 0; i < sizeof(read_buf); i++) { 115 | ASSERT_EQ((char)i, read_buf[i]); 116 | } 117 | ASSERT_TRUE(this->link_tab_.Free(lb)); 118 | } 119 | 120 | TYPED_TEST(ShmLinkTableTest, ReadLen) { 121 | ASSERT_TRUE(this->link_tab_.InitForWrite(kShmKey, kNodeSize, kNodeNum)); 122 | shmc::link_buf_t lb; 123 | char write_buf[1000]; 124 | for (size_t i = 0; i < sizeof(write_buf); i++) { 125 | write_buf[i] = (char)i; 126 | } 127 | ASSERT_TRUE((lb = this->link_tab_.New(write_buf, sizeof(write_buf)))); 128 | // smaller read buf 129 | char read_buf[1000]; 130 | size_t buf_len = sizeof(read_buf) / 2; 131 | ASSERT_TRUE(this->link_tab_.Read(lb, (void*)read_buf, &buf_len)); 132 | ASSERT_EQ(sizeof(read_buf), buf_len); 133 | for (size_t i = 0; i < sizeof(read_buf) / 2; i++) { 134 | ASSERT_EQ((char)i, read_buf[i]); 135 | } 136 | // bigger read buf 137 | buf_len = sizeof(read_buf) * 2; 138 | ASSERT_TRUE(this->link_tab_.Read(lb, (void*)read_buf, &buf_len)); 139 | ASSERT_EQ(sizeof(read_buf), buf_len); 140 | for (size_t i = 0; i < sizeof(read_buf); i++) { 141 | ASSERT_EQ((char)i, read_buf[i]); 142 | } 143 | // std::string as read buf 144 | std::string out; 145 | ASSERT_TRUE(this->link_tab_.Read(lb, &out)); 146 | ASSERT_EQ(sizeof(write_buf), out.size()); 147 | for (size_t i = 0; i < sizeof(write_buf); i++) { 148 | ASSERT_EQ((char)i, out[i]); 149 | } 150 | ASSERT_TRUE(this->link_tab_.Free(lb)); 151 | } 152 | 153 | TYPED_TEST(ShmLinkTableTest, Travel) { 154 | ASSERT_TRUE(this->link_tab_.InitForWrite(kShmKey, kNodeSize, kNodeNum)); 155 | ASSERT_TRUE(this->link_tab_.New("hello", 5)); 156 | ASSERT_TRUE(this->link_tab_.New("hello", 5)); 157 | size_t count = 0; 158 | ASSERT_TRUE(this->link_tab_.Travel([this, &count](shmc::link_buf_t lb) { 159 | std::string out; 160 | ASSERT_TRUE(this->link_tab_.Read(lb, &out)); 161 | ASSERT_EQ("hello", out); 162 | count++; 163 | })); 164 | ASSERT_EQ(2UL, count); 165 | } 166 | 167 | TYPED_TEST(ShmLinkTableTest, HealthCheck) { 168 | ASSERT_TRUE(this->link_tab_.InitForWrite(kShmKey, kNodeSize, kNodeNum)); 169 | typename decltype(this->link_tab_)::HealthStat hstat; 170 | ASSERT_TRUE(this->link_tab_.HealthCheck(&hstat, true)); 171 | EXPECT_EQ(this->link_tab_.free_nodes(), hstat.total_free_nodes); 172 | EXPECT_EQ(0UL, hstat.total_link_bufs); 173 | EXPECT_EQ(0UL, hstat.total_link_buf_bytes); 174 | EXPECT_EQ(0UL, hstat.leaked_nodes); 175 | EXPECT_EQ(0UL, hstat.bad_linked_link_bufs); 176 | EXPECT_EQ(0UL, hstat.too_long_link_bufs); 177 | EXPECT_EQ(0UL, hstat.too_short_link_bufs); 178 | EXPECT_EQ(0UL, hstat.cleared_link_bufs); 179 | EXPECT_EQ(0UL, hstat.recycled_leaked_nodes); 180 | // bad linked 181 | char buf[64]; 182 | shmc::link_buf_t lb; 183 | ASSERT_TRUE((lb = this->link_tab_.New(buf, 40))); 184 | auto node1 = this->InvokeGetNode(lb.head()); 185 | auto node2 = this->InvokeGetNode(node1->next); 186 | node2->tag = 0; 187 | // check again 188 | ASSERT_TRUE(this->link_tab_.HealthCheck(&hstat, true)); 189 | EXPECT_EQ(this->link_tab_.free_nodes(), hstat.total_free_nodes); 190 | EXPECT_EQ(0UL, hstat.total_link_bufs); 191 | EXPECT_EQ(0UL, hstat.total_link_buf_bytes); 192 | EXPECT_EQ(1UL, hstat.leaked_nodes); 193 | EXPECT_EQ(1UL, hstat.bad_linked_link_bufs); 194 | EXPECT_EQ(0UL, hstat.too_long_link_bufs); 195 | EXPECT_EQ(0UL, hstat.too_short_link_bufs); 196 | EXPECT_EQ(1UL, hstat.cleared_link_bufs); 197 | EXPECT_EQ(1UL, hstat.recycled_leaked_nodes); 198 | // too long link buf 199 | ASSERT_TRUE((lb = this->link_tab_.New(buf, 40))); 200 | node1 = this->InvokeGetNode(lb.head()); 201 | *(uint32_t*)node1->user_node = 10; 202 | // check again 203 | ASSERT_TRUE(this->link_tab_.HealthCheck(&hstat, true)); 204 | EXPECT_EQ(this->link_tab_.free_nodes(), hstat.total_free_nodes); 205 | EXPECT_EQ(1UL, hstat.total_link_bufs); 206 | EXPECT_EQ(10UL, hstat.total_link_buf_bytes); 207 | EXPECT_EQ(0UL, hstat.leaked_nodes); 208 | EXPECT_EQ(0UL, hstat.bad_linked_link_bufs); 209 | EXPECT_EQ(1UL, hstat.too_long_link_bufs); 210 | EXPECT_EQ(0UL, hstat.too_short_link_bufs); 211 | EXPECT_EQ(0UL, hstat.cleared_link_bufs); 212 | EXPECT_EQ(0UL, hstat.recycled_leaked_nodes); 213 | ASSERT_TRUE(this->link_tab_.Free(lb)); 214 | // too short link buf 215 | ASSERT_TRUE((lb = this->link_tab_.New(buf, 40))); 216 | node1 = this->InvokeGetNode(lb.head()); 217 | *(uint32_t*)node1->user_node = 80; 218 | // check again 219 | ASSERT_TRUE(this->link_tab_.HealthCheck(&hstat, true)); 220 | EXPECT_EQ(this->link_tab_.free_nodes(), hstat.total_free_nodes); 221 | EXPECT_EQ(0UL, hstat.total_link_bufs); 222 | EXPECT_EQ(0UL, hstat.total_link_buf_bytes); 223 | EXPECT_EQ(0UL, hstat.leaked_nodes); 224 | EXPECT_EQ(0UL, hstat.bad_linked_link_bufs); 225 | EXPECT_EQ(0UL, hstat.too_long_link_bufs); 226 | EXPECT_EQ(1UL, hstat.too_short_link_bufs); 227 | EXPECT_EQ(1UL, hstat.cleared_link_bufs); 228 | EXPECT_EQ(0UL, hstat.recycled_leaked_nodes); 229 | // leaked node 230 | ASSERT_TRUE((lb = this->link_tab_.New(buf, 40))); 231 | node1 = this->InvokeGetNode(lb.head()); 232 | node1->tag = 0; 233 | // check again 234 | ASSERT_TRUE(this->link_tab_.HealthCheck(&hstat, true)); 235 | EXPECT_EQ(this->link_tab_.free_nodes(), hstat.total_free_nodes); 236 | EXPECT_EQ(0UL, hstat.total_link_bufs); 237 | EXPECT_EQ(0UL, hstat.total_link_buf_bytes); 238 | EXPECT_EQ(2UL, hstat.leaked_nodes); 239 | EXPECT_EQ(0UL, hstat.bad_linked_link_bufs); 240 | EXPECT_EQ(0UL, hstat.too_long_link_bufs); 241 | EXPECT_EQ(0UL, hstat.too_short_link_bufs); 242 | EXPECT_EQ(0UL, hstat.cleared_link_bufs); 243 | EXPECT_EQ(2UL, hstat.recycled_leaked_nodes); 244 | // check again 245 | ASSERT_TRUE(this->link_tab_.HealthCheck(&hstat, true)); 246 | EXPECT_EQ(this->link_tab_.free_nodes(), hstat.total_free_nodes); 247 | EXPECT_EQ(0UL, hstat.total_link_bufs); 248 | EXPECT_EQ(0UL, hstat.total_link_buf_bytes); 249 | EXPECT_EQ(0UL, hstat.leaked_nodes); 250 | EXPECT_EQ(0UL, hstat.bad_linked_link_bufs); 251 | EXPECT_EQ(0UL, hstat.too_long_link_bufs); 252 | EXPECT_EQ(0UL, hstat.too_short_link_bufs); 253 | EXPECT_EQ(0UL, hstat.cleared_link_bufs); 254 | EXPECT_EQ(0UL, hstat.recycled_leaked_nodes); 255 | } 256 | 257 | TYPED_PERF_TEST(ShmLinkTableTest, PerfRead_100B) { 258 | static bool is_inited = false; 259 | static shmc::link_buf_t lb; 260 | if (!is_inited) { 261 | is_inited = true; 262 | ASSERT_TRUE(this->link_tab_.InitForWrite(kShmKey, kNodeSize, kNodeNum)) << PERF_ABORT; 263 | char write_buf[100]; 264 | ASSERT_TRUE((lb = this->link_tab_.New(write_buf, sizeof(write_buf)))) << PERF_ABORT; 265 | } 266 | char read_buf[1000]; 267 | size_t buf_len = sizeof(read_buf); 268 | ASSERT_TRUE(this->link_tab_.Read(lb, (void*)read_buf, &buf_len)) << PERF_ABORT; 269 | } 270 | 271 | TYPED_PERF_TEST(ShmLinkTableTest, PerfReadStr_100B) { 272 | static bool is_inited = false; 273 | static shmc::link_buf_t lb; 274 | if (!is_inited) { 275 | is_inited = true; 276 | ASSERT_TRUE(this->link_tab_.InitForWrite(kShmKey, kNodeSize, kNodeNum)) << PERF_ABORT; 277 | char write_buf[100]; 278 | ASSERT_TRUE((lb = this->link_tab_.New(write_buf, sizeof(write_buf)))) << PERF_ABORT; 279 | } 280 | std::string out; 281 | ASSERT_TRUE(this->link_tab_.Read(lb, &out)) << PERF_ABORT; 282 | } 283 | 284 | -------------------------------------------------------------------------------- /test/shm_queue_test.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include 31 | #include 32 | #include 33 | #include "gtestx/gtestx.h" 34 | #include "shmc/shm_queue.h" 35 | #include "shmc/shm_array.h" 36 | 37 | namespace { 38 | 39 | constexpr const char* kShmKey = "0x10007"; 40 | constexpr size_t kQueueBufSize = 1024*1024*1; 41 | 42 | using TestTypes = testing::Types; 44 | 45 | } // namespace 46 | 47 | template 48 | class ShmQueueTest : public testing::Test { 49 | protected: 50 | virtual ~ShmQueueTest() {} 51 | virtual void SetUp() { 52 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 53 | fprintf(stderr, "[%d] %s", lv, s); 54 | }); 55 | this->alloc_.Unlink(kShmKey); 56 | srand(time(nullptr)); 57 | ASSERT_TRUE(this->queue_w_.InitForWrite(kShmKey, kQueueBufSize)); 58 | ASSERT_TRUE(this->queue_r_.InitForRead(kShmKey)); 59 | } 60 | virtual void TearDown() { 61 | this->alloc_.Unlink(kShmKey); 62 | } 63 | shmc::ShmQueue queue_w_; 64 | shmc::ShmQueue queue_r_; 65 | Alloc alloc_; 66 | }; 67 | 68 | // need partial specialization for shmc::ANON/HEAP as InitForRead does not work 69 | template <> 70 | class ShmQueueTest : public testing::Test { 71 | protected: 72 | ShmQueueTest() : queue_r_(queue_w_) {} 73 | virtual ~ShmQueueTest() {} 74 | virtual void SetUp() { 75 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 76 | fprintf(stderr, "[%d] %s", lv, s); 77 | }); 78 | this->alloc_.Unlink(kShmKey); 79 | srand(time(nullptr)); 80 | ASSERT_TRUE(this->queue_w_.InitForWrite(kShmKey, kQueueBufSize)); 81 | } 82 | virtual void TearDown() { 83 | this->alloc_.Unlink(kShmKey); 84 | } 85 | shmc::ShmQueue queue_w_; 86 | shmc::ShmQueue& queue_r_; 87 | shmc::HEAP alloc_; 88 | }; 89 | template <> 90 | class ShmQueueTest : public testing::Test { 91 | protected: 92 | ShmQueueTest() : queue_r_(queue_w_) {} 93 | virtual ~ShmQueueTest() {} 94 | virtual void SetUp() { 95 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 96 | fprintf(stderr, "[%d] %s", lv, s); 97 | }); 98 | this->alloc_.Unlink(kShmKey); 99 | srand(time(nullptr)); 100 | ASSERT_TRUE(this->queue_w_.InitForWrite(kShmKey, kQueueBufSize)); 101 | } 102 | virtual void TearDown() { 103 | this->alloc_.Unlink(kShmKey); 104 | } 105 | shmc::ShmQueue queue_w_; 106 | shmc::ShmQueue& queue_r_; 107 | shmc::HEAP alloc_; 108 | }; 109 | 110 | TYPED_TEST_CASE(ShmQueueTest, TestTypes); 111 | 112 | TYPED_TEST(ShmQueueTest, PushOverloads) { 113 | char buf[] = "hello"; 114 | std::string out; 115 | // Push(const void*, size_t) 116 | ASSERT_TRUE(this->queue_w_.Push(nullptr, 0)); 117 | ASSERT_TRUE(this->queue_r_.Pop(&out)); 118 | ASSERT_TRUE(out.empty()); 119 | ASSERT_TRUE(this->queue_w_.Push(buf, sizeof(buf) - 1)); 120 | ASSERT_TRUE(this->queue_r_.Pop(&out)); 121 | ASSERT_EQ(buf, out); 122 | // Push(const std::string&) 123 | ASSERT_TRUE(this->queue_w_.Push("")); 124 | ASSERT_TRUE(this->queue_r_.Pop(&out)); 125 | ASSERT_TRUE(out.empty()); 126 | std::string src{buf}; 127 | ASSERT_TRUE(this->queue_w_.Push(src)); 128 | ASSERT_TRUE(this->queue_r_.Pop(&out)); 129 | ASSERT_EQ(src, out); 130 | } 131 | 132 | TYPED_TEST(ShmQueueTest, PopOverloads) { 133 | std::string src{"hello"}; 134 | // Pop(void*, size_t*) 135 | char buf[64] = {0}; 136 | size_t len = sizeof(buf); 137 | ASSERT_TRUE(this->queue_w_.Push(src)); 138 | ASSERT_TRUE(this->queue_r_.Pop(buf, &len)); 139 | ASSERT_EQ(src.size(), len); 140 | ASSERT_EQ(src, buf); 141 | ASSERT_FALSE(this->queue_r_.Pop(buf, &len)); 142 | ASSERT_TRUE(this->queue_w_.Push(src)); 143 | len = 4; 144 | ASSERT_FALSE(this->queue_r_.Pop(buf, &len)); 145 | len = 5; 146 | ASSERT_TRUE(this->queue_r_.Pop(buf, &len)); 147 | ASSERT_TRUE(this->queue_w_.Push(src)); 148 | ASSERT_TRUE(this->queue_r_.Pop(nullptr, nullptr)); 149 | ASSERT_FALSE(this->queue_r_.Pop(nullptr, nullptr)); 150 | // Pop(std::string*) 151 | std::string out; 152 | ASSERT_TRUE(this->queue_w_.Push(src)); 153 | ASSERT_TRUE(this->queue_r_.Pop(&out)); 154 | ASSERT_EQ(src, out); 155 | ASSERT_FALSE(this->queue_r_.Pop(&out)); 156 | ASSERT_TRUE(this->queue_w_.Push(src)); 157 | ASSERT_TRUE(this->queue_r_.Pop(nullptr)); 158 | ASSERT_FALSE(this->queue_r_.Pop(nullptr)); 159 | } 160 | 161 | TYPED_TEST(ShmQueueTest, PushThenPopLoop) { 162 | char buf[2048]; 163 | std::string out; 164 | for (int i = 0; i < 100000; i++) { 165 | size_t len = rand() % sizeof(buf); 166 | std::string src{buf, len}; 167 | ASSERT_TRUE(this->queue_w_.Push(src)); 168 | ASSERT_TRUE(this->queue_r_.Pop(&out)); 169 | ASSERT_EQ(src, out); 170 | } 171 | } 172 | 173 | TYPED_TEST(ShmQueueTest, ZeroCopyPushPop) { 174 | char buf[] = "hello"; 175 | size_t len = sizeof(buf) - 1; 176 | typename shmc::ZeroCopyBuf zcb; 177 | // normal 178 | ASSERT_TRUE(this->queue_w_.ZeroCopyPushPrepare(len, &zcb)); 179 | memcpy(zcb.ptr, buf, len); 180 | ASSERT_TRUE(this->queue_w_.ZeroCopyPushCommit(zcb)); 181 | ASSERT_TRUE(this->queue_r_.ZeroCopyPopPrepare(&zcb)); 182 | ASSERT_TRUE(this->queue_r_.ZeroCopyPopCommit(zcb)); 183 | ASSERT_EQ(len, zcb.len); 184 | ASSERT_EQ(0, memcmp(zcb.ptr, buf, len)); 185 | ASSERT_FALSE(this->queue_r_.ZeroCopyPopPrepare(&zcb)); 186 | // commit less 187 | ASSERT_TRUE(this->queue_w_.ZeroCopyPushPrepare(len*2, &zcb)); 188 | ASSERT_EQ(len*2, zcb.len); 189 | zcb.len = len; 190 | memcpy(zcb.ptr, buf, len); 191 | ASSERT_TRUE(this->queue_w_.ZeroCopyPushCommit(zcb)); 192 | ASSERT_TRUE(this->queue_r_.ZeroCopyPopPrepare(&zcb)); 193 | ASSERT_TRUE(this->queue_r_.ZeroCopyPopCommit(zcb)); 194 | ASSERT_EQ(0, memcmp(zcb.ptr, buf, len)); 195 | ASSERT_FALSE(this->queue_r_.ZeroCopyPopPrepare(&zcb)); 196 | } 197 | 198 | namespace { 199 | 200 | using PerfTestTypes = testing::Types; 202 | 203 | } // namespace 204 | 205 | template 206 | class ShmQueueConcPerfTest : public ShmQueueTest { 207 | protected: 208 | struct SharedStatus { 209 | std::atomic write_count; 210 | std::atomic overflow_count; 211 | std::atomic read_count; 212 | std::atomic error_count; 213 | uint64_t total_overflow_count; 214 | uint64_t total_error_count; 215 | }; 216 | static constexpr const char* kSharedShmKey = "0x10008"; 217 | using SharedShmAlloc = shmc::SVIPC; 218 | 219 | virtual ~ShmQueueConcPerfTest() {} 220 | virtual void SetUp() { 221 | ShmQueueTest::SetUp(); 222 | SharedShmAlloc().Unlink(kSharedShmKey); 223 | ASSERT_TRUE(status_.InitForWrite(kSharedShmKey, 1)); 224 | timer_thread_ = std::thread([this] { 225 | unsigned count = 0; 226 | while (!stop_.load(std::memory_order_relaxed)) { 227 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 228 | if (++count % 100 == 0) OnTimer(); 229 | } 230 | EXPECT_EQ(0UL, status_[0].total_overflow_count); 231 | EXPECT_EQ(0UL, status_[0].total_error_count); 232 | }); 233 | if (!(wpid_ = fork())) { 234 | ConsumerProcess(); 235 | exit(0); 236 | } 237 | } 238 | virtual void TearDown() { 239 | kill(wpid_, SIGTERM); 240 | waitpid(wpid_, nullptr, 0); 241 | stop_.store(true, std::memory_order_relaxed); 242 | timer_thread_.join(); 243 | SharedShmAlloc().Unlink(kSharedShmKey); 244 | ShmQueueTest::TearDown(); 245 | } 246 | void ConsumerProcess() { 247 | union { 248 | char buf[1024*16]; 249 | uint64_t seq; 250 | } un; 251 | size_t len; 252 | uint64_t local_read_count = 0; 253 | uint64_t local_error_count = 0; 254 | uint64_t expected_seq = 0; 255 | while (true) { 256 | len = sizeof(un.buf); 257 | if (this->queue_r_.Pop(un.buf, &len)) { 258 | local_read_count++; 259 | if (un.seq != expected_seq) { 260 | local_error_count++; 261 | } 262 | expected_seq = un.seq + 1; 263 | } 264 | if ((local_read_count & 0xff) == 0) { 265 | status_[0].read_count += local_read_count; 266 | status_[0].error_count += local_error_count; 267 | local_read_count = 0; 268 | local_error_count = 0; 269 | } 270 | } 271 | } 272 | void OnTimer() { 273 | std::cout << "write " << status_[0].write_count << "/s " 274 | << "read " << status_[0].read_count << "/s " 275 | << "overflow " << status_[0].overflow_count << "/s " 276 | << "error " << status_[0].error_count << "/s" << std::endl; 277 | status_[0].total_overflow_count += status_[0].overflow_count; 278 | status_[0].total_error_count += status_[0].error_count; 279 | status_[0].write_count.store(0, std::memory_order_relaxed); 280 | status_[0].read_count.store(0, std::memory_order_relaxed); 281 | status_[0].overflow_count.store(0, std::memory_order_relaxed); 282 | status_[0].error_count.store(0, std::memory_order_relaxed); 283 | } 284 | 285 | pid_t wpid_; 286 | std::atomic_bool stop_{false}; 287 | std::thread timer_thread_; 288 | shmc::ShmArray status_; 289 | }; 290 | TYPED_TEST_CASE(ShmQueueConcPerfTest, PerfTestTypes); 291 | 292 | TYPED_PERF_TEST_OPT(ShmQueueConcPerfTest, ConcWrite, 1000000, 1500) { 293 | static uint64_t local_write_count = 0; 294 | static uint64_t local_overflow_count = 0; 295 | static union { 296 | char buf[1024*16]; 297 | uint64_t seq; 298 | } un; 299 | if (this->queue_w_.Push(un.buf, 100)) { 300 | local_write_count++; 301 | un.seq++; 302 | } else { 303 | local_overflow_count++; 304 | } 305 | if ((local_write_count & 0xff) == 0) { 306 | this->status_[0].write_count += local_write_count; 307 | this->status_[0].overflow_count += local_overflow_count; 308 | local_write_count = 0; 309 | local_overflow_count = 0; 310 | } 311 | } 312 | 313 | -------------------------------------------------------------------------------- /test/shm_sync_buf_test.cc: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016-2017, Bin Wei 2 | * All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above 11 | * copyright notice, this list of conditions and the following disclaimer 12 | * in the documentation and/or other materials provided with the 13 | * distribution. 14 | * * The names of its contributors may not be used to endorse or 15 | * promote products derived from this software without specific prior 16 | * written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include 31 | #include "gtestx/gtestx.h" 32 | #include "shmc/shm_sync_buf.h" 33 | 34 | namespace { 35 | 36 | constexpr const char* kShmKey = "0x10005"; 37 | constexpr size_t kSyncBufSize = 1024*1024*100; 38 | 39 | using TestTypes = testing::Types; 41 | 42 | } // namespace 43 | 44 | template 45 | class ShmSyncBufTest : public testing::Test { 46 | protected: 47 | virtual ~ShmSyncBufTest() {} 48 | virtual void SetUp() { 49 | shmc::SetLogHandler(shmc::kDebug, [](shmc::LogLevel lv, const char* s) { 50 | fprintf(stderr, "[%d] %s", lv, s); 51 | }); 52 | this->alloc_.Unlink(kShmKey); 53 | ASSERT_TRUE(this->sync_buf_.InitForWrite(kShmKey, kSyncBufSize)); 54 | if (shmc::impl::AllocTraits::is_named) { 55 | this->sync_buf_ro_ = &this->sync_buf2_; 56 | ASSERT_TRUE(this->sync_buf_ro_->InitForRead(kShmKey)); 57 | } else { 58 | this->sync_buf_ro_ = &this->sync_buf_; 59 | } 60 | } 61 | virtual void TearDown() { 62 | this->alloc_.Unlink(kShmKey); 63 | } 64 | shmc::ShmSyncBuf sync_buf_; 65 | shmc::ShmSyncBuf sync_buf2_; 66 | shmc::ShmSyncBuf* sync_buf_ro_; 67 | Alloc alloc_; 68 | }; 69 | TYPED_TEST_CASE(ShmSyncBufTest, TestTypes); 70 | 71 | TYPED_TEST(ShmSyncBufTest, Init) { 72 | } 73 | 74 | TYPED_TEST(ShmSyncBufTest, PushRead) { 75 | ASSERT_TRUE(this->sync_buf_.Push("hello", 5)); 76 | shmc::SyncIter it = this->sync_buf_.Head(); 77 | char buf[6] = {0}; 78 | size_t len = sizeof(buf); 79 | shmc::SyncMeta meta; 80 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, buf, &len)); 81 | ASSERT_EQ(0, strncmp(buf, "hello", sizeof(buf))); 82 | std::string out; 83 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, &out)); 84 | ASSERT_EQ("hello", out); 85 | ASSERT_TRUE(this->sync_buf_.Next(&it)); 86 | ASSERT_EQ(0, this->sync_buf_.Read(it, &meta, buf, &len)); 87 | ASSERT_EQ(0, this->sync_buf_.Read(it, &meta, &out)); 88 | ASSERT_TRUE(this->sync_buf_.Push("world", 5)); 89 | len = sizeof(buf); 90 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, buf, &len)); 91 | ASSERT_EQ(0, strncmp(buf, "world", sizeof(buf))); 92 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, &out)); 93 | ASSERT_EQ("world", out); 94 | } 95 | 96 | TYPED_TEST(ShmSyncBufTest, FindBySeq) { 97 | ASSERT_TRUE(this->sync_buf_.Push("hello 0", 7)); 98 | ASSERT_TRUE(this->sync_buf_.Push("hello 1", 7)); 99 | ASSERT_TRUE(this->sync_buf_.Push("hello 2", 7)); 100 | ASSERT_TRUE(this->sync_buf_.Push("hello 3", 7)); 101 | ASSERT_TRUE(this->sync_buf_.Push("hello 4", 7)); 102 | ASSERT_TRUE(this->sync_buf_.Push("hello 5", 7)); 103 | shmc::SyncMeta meta; 104 | std::string out; 105 | ASSERT_EQ(1, this->sync_buf_.Read(this->sync_buf_.Head(), &meta, &out)); 106 | ASSERT_EQ("hello 0", out); 107 | shmc::SyncIter it; 108 | ASSERT_FALSE(this->sync_buf_.FindBySeq(meta.seq-1, &it)); 109 | ASSERT_FALSE(this->sync_buf_.FindBySeq(meta.seq+6, &it)); 110 | uint64_t seq = meta.seq; 111 | for (size_t i = 0; i <= 5; i++, seq++) { 112 | ASSERT_TRUE(this->sync_buf_.FindBySeq(seq, &it)); 113 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, &out)); 114 | char expected[10]; 115 | snprintf(expected, sizeof(expected), "hello %lu", i); 116 | ASSERT_EQ(expected, out); 117 | } 118 | } 119 | 120 | TYPED_TEST(ShmSyncBufTest, FindByTime) { 121 | shmc::SyncIter it; 122 | shmc::SyncMeta meta; 123 | std::string out; 124 | ASSERT_FALSE(this->sync_buf_.FindByTime({0, 0}, &it)); 125 | ASSERT_TRUE(this->sync_buf_.Push("hello 0", 7, {0, 1})); 126 | ASSERT_TRUE(this->sync_buf_.Push("hello 1", 7, {1, 0})); 127 | ASSERT_TRUE(this->sync_buf_.Push("hello 2", 7, {2, 0})); 128 | ASSERT_TRUE(this->sync_buf_.FindByTime({0, 0}, &it)); 129 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, &out)); 130 | ASSERT_EQ("hello 0", out); 131 | ASSERT_TRUE(this->sync_buf_.FindByTime({0, 1}, &it)); 132 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, &out)); 133 | ASSERT_EQ("hello 0", out); 134 | ASSERT_TRUE(this->sync_buf_.FindByTime({0, 2}, &it)); 135 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, &out)); 136 | ASSERT_EQ("hello 1", out); 137 | ASSERT_TRUE(this->sync_buf_.FindByTime({1, 0}, &it)); 138 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, &out)); 139 | ASSERT_EQ("hello 1", out); 140 | ASSERT_TRUE(this->sync_buf_.FindByTime({1, 1}, &it)); 141 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, &out)); 142 | ASSERT_EQ("hello 2", out); 143 | ASSERT_TRUE(this->sync_buf_.FindByTime({2, 0}, &it)); 144 | ASSERT_EQ(1, this->sync_buf_.Read(it, &meta, &out)); 145 | ASSERT_EQ("hello 2", out); 146 | ASSERT_FALSE(this->sync_buf_.FindByTime({2, 1}, &it)); 147 | } 148 | 149 | // Push perf 150 | 151 | template 152 | class ShmSyncBufPerfTest : public ShmSyncBufTest { 153 | protected: 154 | virtual ~ShmSyncBufPerfTest() {} 155 | virtual void SetUp() { 156 | ShmSyncBufTest::SetUp(); 157 | } 158 | virtual void TearDown() { 159 | ShmSyncBufTest::TearDown(); 160 | } 161 | }; 162 | TYPED_TEST_CASE(ShmSyncBufPerfTest, TestTypes); 163 | 164 | TYPED_PERF_TEST(ShmSyncBufPerfTest, Push_10B) { 165 | char buf[10]; 166 | this->sync_buf_.Push(buf, sizeof(buf)); 167 | } 168 | 169 | TYPED_PERF_TEST(ShmSyncBufPerfTest, Push_100B) { 170 | char buf[100]; 171 | this->sync_buf_.Push(buf, sizeof(buf)); 172 | } 173 | 174 | TYPED_PERF_TEST(ShmSyncBufPerfTest, Push_1000B) { 175 | char buf[1000]; 176 | this->sync_buf_.Push(buf, sizeof(buf)); 177 | } 178 | 179 | // Read perf 180 | 181 | namespace { 182 | 183 | using ReadPerfTestTypes = testing::Types; 185 | 186 | } // namespace 187 | 188 | template 189 | class ShmSyncBufReadPerfTest : public ShmSyncBufPerfTest { 190 | protected: 191 | virtual ~ShmSyncBufReadPerfTest() {} 192 | virtual void SetUp() { 193 | ShmSyncBufPerfTest::SetUp(); 194 | if (!(wpid_ = fork())) { 195 | WriteProcess(); 196 | exit(0); 197 | } 198 | it_ = this->sync_buf_ro_->Head(); 199 | } 200 | virtual void TearDown() { 201 | kill(wpid_, SIGTERM); 202 | waitpid(wpid_, nullptr, 0); 203 | ShmSyncBufPerfTest::TearDown(); 204 | } 205 | void WriteProcess() { 206 | srand(time(NULL)); 207 | char buf[200]; 208 | size_t count = 0; 209 | while (true) { 210 | size_t n = rand() % sizeof(buf) + 1; 211 | for (size_t i = 0; i < n; i++) buf[i] = (char)n; 212 | if (!this->sync_buf_.Push(buf, n)) { 213 | std::cerr << "Push fail! len=" << n << std::endl; 214 | } 215 | if (++count >= 100) { 216 | usleep(1000); 217 | count = 0; 218 | } 219 | } 220 | } 221 | pid_t wpid_; 222 | shmc::SyncIter it_; 223 | }; 224 | TYPED_TEST_CASE(ShmSyncBufReadPerfTest, ReadPerfTestTypes); 225 | 226 | TYPED_PERF_TEST(ShmSyncBufReadPerfTest, ConcReadCheck) { 227 | shmc::SyncMeta meta; 228 | char buf[1000]; 229 | size_t len; 230 | int ret; 231 | do { 232 | len = sizeof(buf); 233 | ret = this->sync_buf_ro_->Read(this->it_, &meta, buf, &len); 234 | } while (ret == 0); 235 | ASSERT_EQ(1, ret) << PERF_ABORT; 236 | ASSERT_LE(len, sizeof(buf)) << PERF_ABORT; 237 | for (size_t i = 0; i < len; i++) { 238 | ASSERT_EQ((char)len, buf[i]) << PERF_ABORT; 239 | } 240 | ASSERT_TRUE(this->sync_buf_ro_->Next(&this->it_)) << PERF_ABORT; 241 | } 242 | 243 | TYPED_PERF_TEST(ShmSyncBufReadPerfTest, ConcReadLoop) { 244 | shmc::SyncMeta meta; 245 | char buf[1000]; 246 | size_t len = sizeof(buf); 247 | int ret = this->sync_buf_ro_->Read(this->it_, &meta, buf, &len); 248 | ASSERT_TRUE(ret == 1 || ret == 0) << PERF_ABORT; 249 | if (ret == 1) { 250 | ASSERT_TRUE(this->sync_buf_ro_->Next(&this->it_)) << PERF_ABORT; 251 | } else { 252 | this->it_ = this->sync_buf_ro_->Head(); 253 | } 254 | } 255 | 256 | TYPED_PERF_TEST(ShmSyncBufReadPerfTest, ConcReadStringLoop) { 257 | shmc::SyncMeta meta; 258 | std::string out; 259 | int ret = this->sync_buf_ro_->Read(this->it_, &meta, &out); 260 | ASSERT_TRUE(ret == 1 || ret == 0) << PERF_ABORT; 261 | if (ret == 1) { 262 | ASSERT_TRUE(this->sync_buf_ro_->Next(&this->it_)) << PERF_ABORT; 263 | } else { 264 | this->it_ = this->sync_buf_ro_->Head(); 265 | } 266 | } 267 | 268 | TYPED_PERF_TEST(ShmSyncBufReadPerfTest, ConcReadMetaLoop) { 269 | shmc::SyncMeta meta; 270 | uint32_t val; 271 | size_t len = sizeof(val); 272 | int ret = this->sync_buf_ro_->Read(this->it_, &meta, &val, &len); 273 | ASSERT_TRUE(ret == 1 || ret == 0) << PERF_ABORT; 274 | if (ret == 1) { 275 | ASSERT_TRUE(this->sync_buf_ro_->Next(&this->it_)) << PERF_ABORT; 276 | } else { 277 | this->it_ = this->sync_buf_ro_->Head(); 278 | } 279 | } 280 | --------------------------------------------------------------------------------