├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── read_mmap.cc /.gitignore: -------------------------------------------------------------------------------- 1 | read_mmap 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sublime HQ Pty. Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = g++ 2 | 3 | read_mmap: read_mmap.cc 4 | $(CC) -Wall -O3 -o read_mmap read_mmap.cc 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Robust mmap Example 2 | 3 | This is a small example repository for a robust mmap implementation 4 | -------------------------------------------------------------------------------- /read_mmap.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * We want to read arbitrary integers from a file. This is a very small example 3 | * of what handling a binary file in a long running process might look like, 4 | * meant to simulate what it's like reading git object files in Sublime Merge. 5 | * 6 | * This is only tested for linux, however it may compile/run on mac/windows. 7 | */ 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #if defined(_WIN32) 15 | #include 16 | #else 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #endif 24 | 25 | #if defined(_WIN32) 26 | void install_signal_handlers() {} 27 | #else 28 | // Keep track of this thread's jump point and whether it's set 29 | thread_local volatile bool sigbus_jmp_set; 30 | thread_local sigjmp_buf sigbus_jmp_buf; 31 | 32 | static void handle_sigbus(int c) { 33 | // Only handle the signal if the jump point is set on this thread 34 | if (sigbus_jmp_set) { 35 | sigbus_jmp_set = false; 36 | 37 | // siglongjmp out of the signal handler, returning the signal 38 | siglongjmp(sigbus_jmp_buf, c); 39 | } 40 | } 41 | 42 | void install_signal_handlers() { 43 | // Install signal handler for SIGBUS 44 | struct sigaction act; 45 | act.sa_handler = &handle_sigbus; 46 | 47 | // SA_NODEFER is required due to siglongjmp 48 | act.sa_flags = SA_NODEFER; 49 | sigemptyset(&act.sa_mask); // Don't block any signals 50 | 51 | // Connect the signal 52 | sigaction(SIGBUS, &act, nullptr); 53 | } 54 | #endif 55 | 56 | 57 | template 58 | bool safe_mmap_try(F fn) { 59 | #if defined(_WIN32) 60 | __try { 61 | fn(); 62 | return true; 63 | } __except( 64 | GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR 65 | ? EXCEPTION_EXECUTE_HANDLER 66 | : EXCEPTION_CONTINUE_SEARCH) { 67 | return false; 68 | } 69 | #else 70 | // Make sure we don't call safe_mmap_try from fn 71 | assert(!sigbus_jmp_set); 72 | 73 | sigbus_jmp_set = true; 74 | 75 | // sigsetjmp to handle SIGBUS. Do not save the signal mask 76 | if (sigsetjmp(sigbus_jmp_buf, 0) == 0) { 77 | // Call the lambda 78 | fn(); 79 | 80 | // Notify that a jmp point has been set. 81 | sigbus_jmp_set = false; 82 | return true; 83 | } else { 84 | sigbus_jmp_set = false; 85 | return false; 86 | } 87 | #endif 88 | } 89 | 90 | struct file { 91 | const size_t size; 92 | const void* data; 93 | 94 | // File constructor 95 | file(size_t s, void* d) : size(s), data(d) { 96 | } 97 | 98 | // Virtual file destructor so we can override per system 99 | virtual ~file() {} 100 | 101 | // Get a 64 bit integer at the byte offset 102 | bool read(size_t offset, int64_t * result) { 103 | // Out of bounds check 104 | assert(offset <= size - sizeof(int64_t)); 105 | 106 | return safe_mmap_try([&]() { 107 | *result = *(int64_t*)((int8_t*)data + offset); 108 | }); 109 | } 110 | }; 111 | 112 | #if defined(_WIN32) 113 | struct windows_file : public file { 114 | HANDLE win_handle; 115 | 116 | windows_file(HANDLE h, size_t s, void* d) : file(s, d), win_handle(h) { 117 | } 118 | 119 | virtual ~windows_file() { 120 | // Need to unmap, then close 121 | UnmapViewOfFile(data); 122 | CloseHandle(win_handle); 123 | } 124 | }; 125 | 126 | file* open_file(const char * path) { 127 | // Create a normal file handle 128 | HANDLE f = CreateFile( 129 | path, 130 | GENERIC_READ, 131 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 132 | nullptr, 133 | OPEN_EXISTING, 134 | 0, 135 | nullptr); 136 | if (f == INVALID_HANDLE_VALUE) 137 | return nullptr; 138 | 139 | // Get the size of the file 140 | size_t size = 0; 141 | 142 | LARGE_INTEGER i; 143 | if (GetFileSizeEx(f, &i)) { 144 | size = (size_t)i.QuadPart; 145 | } else { 146 | CloseHandle(f); 147 | return nullptr; 148 | } 149 | 150 | // Create a file mapping, needed for a map view 151 | HANDLE hmap = CreateFileMapping(f, nullptr, PAGE_READONLY, 0, 0, nullptr); 152 | 153 | if (!hmap) 154 | return nullptr; 155 | 156 | // Actually memory map the file 157 | void* data = MapViewOfFile(hmap, FILE_MAP_READ, 0, 0, size); 158 | 159 | // Close the regular file handle, keep hmap around 160 | CloseHandle(f); 161 | 162 | if (!data) { 163 | CloseHandle(hmap); 164 | return nullptr; 165 | } 166 | 167 | return new windows_file(hmap, size, data); 168 | } 169 | #else 170 | struct posix_file : public file { 171 | using file::file; 172 | 173 | virtual ~posix_file() { 174 | munmap((void*)data, size); 175 | } 176 | }; 177 | 178 | file* open_file(const char * path) { 179 | // Stat the file to get the size for later 180 | struct stat64 st; 181 | 182 | if (stat64(path, &st)) 183 | return nullptr; 184 | 185 | // Open the file in read only mode 186 | int fd = open(path, O_RDONLY); 187 | if (fd < 0) 188 | return nullptr; 189 | 190 | // Allocate a buffer for the file contents 191 | void* data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 192 | 193 | // mmap returns MAP_FAILED on error, not NULL 194 | if (data == MAP_FAILED) 195 | return nullptr; 196 | 197 | // Construct a new file with the data 198 | return new posix_file(st.st_size, data); 199 | } 200 | #endif 201 | 202 | int main(int argc, char const *argv[]) { 203 | // Assume we're given 1 argument 204 | if (argc != 2) { 205 | return 1; 206 | } 207 | 208 | install_signal_handlers(); 209 | 210 | // Open the requested file 211 | file* f = open_file(argv[1]); 212 | 213 | // Setup some random number generation 214 | std::mt19937 rng; 215 | rng.seed(std::random_device()()); 216 | auto random = std::uniform_int_distribution( 217 | 0, f->size - sizeof(int64_t)); 218 | 219 | // Continuously read from a random location 220 | while (true) { 221 | size_t offset = (size_t) random(rng); 222 | 223 | // Get the number at the offset 224 | int64_t value; 225 | if (f->read(offset, &value)) { 226 | // Print out the number 227 | std::cout << value << std::endl; 228 | } else { 229 | std::cout << "Failed to read" << std::endl; 230 | } 231 | } 232 | 233 | delete f; 234 | 235 | return 0; 236 | } 237 | --------------------------------------------------------------------------------