/src/hobbes/include/hobbes/storage.H
Line | Count | Source |
1 | | /* |
2 | | * storage : structured storage of application data |
3 | | * |
4 | | * use DEFINE_STORAGE_GROUP(G, C, QoS, T) to create storage group / transaction context |
5 | | * use DECLARE_STORAGE_GROUP(G) to forward-declare the storage group G (suitable for declaration in program headers |
6 | | * use HSTORE(G,N,V0,V1,...) to record the data V0,V1,... with the name N in the group G |
7 | | * use HLOG (G,N,"text display",V0,V1,...) to record the data V0,V1,... with the name N in the group G (with the display hint "text display" to reconstruct text) |
8 | | * |
9 | | */ |
10 | | |
11 | | #ifndef HSTORE_H_INCLUDED |
12 | | #define HSTORE_H_INCLUDED |
13 | | |
14 | | #include <array> |
15 | | #include <atomic> |
16 | | #include <cassert> |
17 | | #include <cstdint> |
18 | | #include <cstdlib> |
19 | | #include <cstring> |
20 | | #include <functional> |
21 | | #include <iostream> |
22 | | #include <map> |
23 | | #include <mutex> |
24 | | #include <sstream> |
25 | | #include <stdexcept> |
26 | | #include <string> |
27 | | #include <tuple> |
28 | | #include <type_traits> |
29 | | #include <vector> |
30 | | |
31 | | #include <unistd.h> |
32 | | #include <sys/types.h> |
33 | | #include <sys/stat.h> |
34 | | #include <sys/mman.h> |
35 | | #include <sys/un.h> |
36 | | #include <sys/socket.h> |
37 | | #include <sys/ioctl.h> |
38 | | #include <sys/time.h> |
39 | | #include <netinet/in.h> |
40 | | #include <netdb.h> |
41 | | #include <fcntl.h> |
42 | | |
43 | | // types with static reflection info (for reflective structs, variants, etc) |
44 | | #include "reflect.H" |
45 | | |
46 | | namespace hobbes { namespace storage { namespace internal { |
47 | | |
48 | | namespace spin { |
49 | | |
50 | | #if defined(CLOCK_REALTIME) |
51 | 0 | inline long poll_tickNS() { |
52 | 0 | timespec ts; |
53 | 0 | if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { |
54 | 0 | return ts.tv_sec * 1000000000L + ts.tv_nsec; |
55 | 0 | } else { |
56 | 0 | return 0; |
57 | 0 | } |
58 | 0 | } |
59 | | #else |
60 | | inline long poll_tickNS() { |
61 | | struct timeval t; |
62 | | if (gettimeofday(&t, 0) == 0) { |
63 | | return ((t.tv_sec*1000000)+t.tv_usec)*1000; |
64 | | } else { |
65 | | return 0; |
66 | | } |
67 | | } |
68 | | #endif |
69 | | |
70 | 0 | static inline void wakeN(volatile uint32_t*, int) { |
71 | 0 | // assume the waiter is polling, nothing to do |
72 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::internal::spin::wakeN(unsigned int volatile*, int) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::internal::spin::wakeN(unsigned int volatile*, int) |
73 | | |
74 | | } // namespace spin |
75 | | }}} // namespace hobbes { namespace storage { namespace internal |
76 | | |
77 | | // a few things have to be OS-specific here |
78 | | // * available waiting strategy in shared memory |
79 | | // * default waiting strategy |
80 | | #if defined(__APPLE__) && defined(__MACH__) |
81 | | // macOS doesn't support this flag to mmap so we can just 0 it out |
82 | | #ifndef MAP_POPULATE |
83 | | #define MAP_POPULATE 0 |
84 | | #endif |
85 | | |
86 | | namespace hobbes { namespace storage { namespace internal { |
87 | | |
88 | | namespace spin { |
89 | | |
90 | | static inline void waitForUpdate(volatile uint32_t* p, int eqV) { |
91 | | while (true) { |
92 | | for (size_t c = 0; c < 4096; ++c) { |
93 | | if (*p != eqV) { |
94 | | return; |
95 | | } |
96 | | } |
97 | | usleep(500); |
98 | | } |
99 | | } |
100 | | static inline void waitForUpdate(volatile uint32_t* p, int eqV, size_t timeoutNS, const std::function<void()>& timeoutF) { |
101 | | if (timeoutNS == 0) { |
102 | | waitForUpdate(p, eqV); |
103 | | return; |
104 | | } |
105 | | |
106 | | long t0 = poll_tickNS(); |
107 | | |
108 | | while (true) { |
109 | | for (size_t c = 0; c < 4096; ++c) { |
110 | | if (*p != eqV) { |
111 | | return; |
112 | | } |
113 | | } |
114 | | |
115 | | long t1 = poll_tickNS(); |
116 | | if ((t1-t0) >= timeoutNS) { |
117 | | timeoutF(); |
118 | | t0 = t1; |
119 | | } |
120 | | usleep(500); |
121 | | } |
122 | | } |
123 | | |
124 | | } // namespace spin |
125 | | |
126 | | namespace platform = spin; |
127 | | |
128 | | }}} // namespace hobbes { namespace storage { namespace internal |
129 | | |
130 | | #else |
131 | | |
132 | | #include <linux/futex.h> |
133 | | #include <sys/syscall.h> |
134 | | |
135 | | namespace hobbes { namespace storage { namespace internal { |
136 | | |
137 | | namespace spin { |
138 | | |
139 | 0 | static inline void waitForUpdate(volatile uint32_t* p, int eqV) { |
140 | 0 | while (true) { |
141 | 0 | for (size_t c = 0; c < 4096; ++c) { |
142 | 0 | if (*p != uint32_t(eqV)) { |
143 | 0 | return; |
144 | 0 | } |
145 | 0 | } |
146 | 0 | } |
147 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::internal::spin::waitForUpdate(unsigned int volatile*, int) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::internal::spin::waitForUpdate(unsigned int volatile*, int) |
148 | 0 | static inline void waitForUpdate(volatile uint32_t* p, int eqV, size_t timeoutNS, const std::function<void()>& timeoutF) { |
149 | 0 | if (timeoutNS == 0) { |
150 | 0 | waitForUpdate(p, eqV); |
151 | 0 | return; |
152 | 0 | } |
153 | 0 |
|
154 | 0 | long t0 = poll_tickNS(); |
155 | 0 |
|
156 | 0 | while (true) { |
157 | 0 | for (size_t c = 0; c < 4096; ++c) { |
158 | 0 | if (*p != uint32_t(eqV)) { |
159 | 0 | return; |
160 | 0 | } |
161 | 0 | } |
162 | 0 |
|
163 | 0 | long t1 = poll_tickNS(); |
164 | 0 | if ((t1-t0) >= long(timeoutNS)) { |
165 | 0 | timeoutF(); |
166 | 0 | t0 = t1; |
167 | 0 | } |
168 | 0 | } |
169 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::internal::spin::waitForUpdate(unsigned int volatile*, int, unsigned long, std::function<void ()> const&) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::internal::spin::waitForUpdate(unsigned int volatile*, int, unsigned long, std::function<void ()> const&) |
170 | | } // namespace spin |
171 | | |
172 | | namespace futex { |
173 | | |
174 | 0 | static inline long sys_futex(volatile uint32_t* p, int op, int v, struct timespec* timeout, void* p2, int v2) { |
175 | 0 | return syscall(SYS_futex, p, op, v, timeout, p2, v2); |
176 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::internal::futex::sys_futex(unsigned int volatile*, int, int, timespec*, void*, int) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::internal::futex::sys_futex(unsigned int volatile*, int, int, timespec*, void*, int) |
177 | 0 | static inline void waitForUpdate(volatile uint32_t* p, int eqV) { |
178 | 0 | sys_futex(p, FUTEX_WAIT, eqV, nullptr, nullptr, 0); |
179 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::internal::futex::waitForUpdate(unsigned int volatile*, int) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::internal::futex::waitForUpdate(unsigned int volatile*, int) |
180 | 0 | static inline void waitForUpdate(volatile uint32_t* p, int eqV, size_t timeoutNS, const std::function<void()>& timeoutF) { |
181 | 0 | if (timeoutNS == 0) { |
182 | 0 | waitForUpdate(p, eqV); |
183 | 0 | return; |
184 | 0 | } |
185 | 0 |
|
186 | 0 | struct timespec ts; |
187 | 0 | ts.tv_sec = timeoutNS / 1000000000L; |
188 | 0 | ts.tv_nsec = timeoutNS % 1000000000L; |
189 | 0 |
|
190 | 0 | bool c = true; |
191 | 0 | while (c) { |
192 | 0 | int r = sys_futex(p, FUTEX_WAIT, eqV, &ts, nullptr, 0); |
193 | 0 | c = r < 0 && errno == ETIMEDOUT; |
194 | 0 |
|
195 | 0 | if (c) { |
196 | 0 | timeoutF(); |
197 | 0 | } |
198 | 0 | } |
199 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::internal::futex::waitForUpdate(unsigned int volatile*, int, unsigned long, std::function<void ()> const&) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::internal::futex::waitForUpdate(unsigned int volatile*, int, unsigned long, std::function<void ()> const&) |
200 | 0 | static inline void wakeN(volatile uint32_t* p, int c) { |
201 | 0 | sys_futex(p, FUTEX_WAKE, c, nullptr, nullptr, 0); |
202 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::internal::futex::wakeN(unsigned int volatile*, int) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::internal::futex::wakeN(unsigned int volatile*, int) |
203 | | |
204 | | } // namespace futex |
205 | | |
206 | | namespace platform = futex; |
207 | | |
208 | | }}} // namespace hobbes { namespace storage { namespace internal |
209 | | #endif |
210 | | |
211 | | namespace hobbes { namespace storage { |
212 | | |
213 | | using WaitFn = void (*)(volatile uint32_t *, int, size_t, const std::function<void ()> &); |
214 | | using WakeFn = void (*)(volatile uint32_t *, int); |
215 | | |
216 | | enum WaitPolicy { |
217 | | Platform = 0, |
218 | | Spin, |
219 | | }; |
220 | | |
221 | 0 | static inline WaitFn waitFn(const WaitPolicy s) { |
222 | 0 | switch (s) { |
223 | 0 | case Spin: return &internal::spin::waitForUpdate; |
224 | 0 | case Platform: |
225 | 0 | default: return &internal::platform::waitForUpdate; |
226 | 0 | } |
227 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::waitFn(hobbes::storage::WaitPolicy) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::waitFn(hobbes::storage::WaitPolicy) |
228 | | |
229 | 0 | static inline WakeFn wakeFn(const WaitPolicy s) { |
230 | 0 | switch (s) { |
231 | 0 | case Spin: return &internal::spin::wakeN; |
232 | 0 | case Platform: |
233 | 0 | default: return &internal::platform::wakeN; |
234 | 0 | } |
235 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::wakeFn(hobbes::storage::WaitPolicy) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::wakeFn(hobbes::storage::WaitPolicy) |
236 | | |
237 | | }} |
238 | | |
239 | | namespace hobbes { namespace storage { |
240 | | |
241 | | #define HSTORE_VERSION static_cast<uint32_t>(0x00020000) |
242 | | |
243 | | using bytes = std::vector<uint8_t>; |
244 | | |
245 | | // write transactions into shared memory |
246 | | #define PRIV_HSTORE_LIKELY(x) __builtin_expect((x),1) |
247 | | #define PRIV_HSTORE_UNLIKELY(x) __builtin_expect((x),0) |
248 | | |
249 | | template <typename T> |
250 | 0 | T align(T x, T m) { |
251 | 0 | if (m == 0 || (x % m) == 0) { |
252 | 0 | return x; |
253 | 0 | } else { |
254 | 0 | return (1 + (x / m)) * m; |
255 | 0 | } |
256 | 0 | } |
257 | | |
258 | | #define PRIV_HSTORE_SPIN_MIN 512 |
259 | | #define PRIV_HSTORE_SPIN_MAX 524288 |
260 | | |
261 | 0 | static inline unsigned spin(unsigned count) { |
262 | 0 | for (volatile unsigned i = 0; i != count; ++i); |
263 | 0 | return (count << 1); |
264 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::spin(unsigned int) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::spin(unsigned int) |
265 | | |
266 | | #if defined(__aarch64__) |
267 | | //https://docs.huihoo.com/doxygen/linux/kernel/3.7/arch_2arm64_2include_2asm_2cmpxchg_8h_source.html |
268 | | #define uxchg(ptr,x) \ |
269 | | __xchg((x),(ptr),sizeof(*(ptr))) |
270 | | |
271 | | static inline void __xchg(volatile unsigned long x, volatile void *ptr, std::size_t size) |
272 | | { |
273 | | unsigned long ret, tmp; |
274 | | |
275 | | switch (size) { |
276 | | case 1: |
277 | | asm volatile("// __xchg1\n" |
278 | | "1: ldaxrb %w0, [%3]\n" |
279 | | " stlxrb %w1, %w2, [%3]\n" |
280 | | " cbnz %w1, 1b\n" |
281 | | : "=&r" (ret), "=&r" (tmp) |
282 | | : "r" (x), "r" (ptr) |
283 | | : "memory", "cc"); |
284 | | break; |
285 | | case 2: |
286 | | asm volatile("// __xchg2\n" |
287 | | "1: ldaxrh %w0, [%3]\n" |
288 | | " stlxrh %w1, %w2, [%3]\n" |
289 | | " cbnz %w1, 1b\n" |
290 | | : "=&r" (ret), "=&r" (tmp) |
291 | | : "r" (x), "r" (ptr) |
292 | | : "memory", "cc"); |
293 | | break; |
294 | | case 4: |
295 | | asm volatile("// __xchg4\n" |
296 | | "1: ldaxr %w0, [%3]\n" |
297 | | " stlxr %w1, %w2, [%3]\n" |
298 | | " cbnz %w1, 1b\n" |
299 | | : "=&r" (ret), "=&r" (tmp) |
300 | | : "r" (x), "r" (ptr) |
301 | | : "memory", "cc"); |
302 | | break; |
303 | | case 8: |
304 | | asm volatile("// __xchg8\n" |
305 | | "1: ldaxr %0, [%3]\n" |
306 | | " stlxr %w1, %2, [%3]\n" |
307 | | " cbnz %w1, 1b\n" |
308 | | : "=&r" (ret), "=&r" (tmp) |
309 | | : "r" (x), "r" (ptr) |
310 | | : "memory", "cc"); |
311 | | break; |
312 | | default: |
313 | | assert(0); |
314 | | } |
315 | | } |
316 | | #else |
317 | 0 | static inline void uxchg(volatile uint32_t* px, uint32_t nx) { |
318 | 0 | __asm__ __volatile__( |
319 | 0 | "xchgl %0,%1" |
320 | 0 | :"=r" (nx) |
321 | 0 | :"m" (*px), "0" (nx) |
322 | 0 | :"memory" |
323 | 0 | ); |
324 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::uxchg(unsigned int volatile*, unsigned int) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::uxchg(unsigned int volatile*, unsigned int) |
325 | | #endif |
326 | | |
327 | | #define xchg __sync_lock_test_and_set |
328 | | |
329 | | // define a local socket for registering new storage queues |
330 | 0 | inline void mqwrite(int fd, const uint8_t* x, size_t len) { |
331 | 0 | size_t i = 0; |
332 | 0 | while (i < len) { |
333 | 0 | ssize_t c = write(fd, x + i, len - i); |
334 | 0 | if (c < 0) { |
335 | 0 | if (errno != EINTR) { |
336 | 0 | throw std::runtime_error("Couldn't write to socket: " + std::string(strerror(errno))); |
337 | 0 | } |
338 | 0 | } else { |
339 | 0 | i += c; |
340 | 0 | } |
341 | 0 | } |
342 | 0 | } |
343 | | |
344 | 0 | inline void mqread(int fd, uint8_t* x, size_t len) { |
345 | 0 | size_t i = 0; |
346 | 0 | while (i < len) { |
347 | 0 | auto ret = read(fd , x + i, len - i); |
348 | 0 | if (ret < 0 && errno != EINTR) { |
349 | 0 | throw std::runtime_error("Couldn't read from socket: " + std::string(strerror(errno))); |
350 | 0 | } else { |
351 | 0 | i += ret; |
352 | 0 | } |
353 | 0 | } |
354 | 0 | } |
355 | | |
356 | 0 | inline int mqconnect(const std::string& fileName) { |
357 | 0 | int r = socket(AF_UNIX, SOCK_STREAM, 0); |
358 | 0 | if (r == -1) { |
359 | 0 | throw std::runtime_error("Unable to allocate socket: " + std::string(strerror(errno))); |
360 | 0 | } |
361 | 0 | |
362 | 0 | sockaddr_un addr; |
363 | 0 | memset(&addr, 0, sizeof(addr)); |
364 | 0 | addr.sun_family = AF_UNIX; |
365 | 0 | snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", fileName.c_str()); |
366 | 0 | |
367 | 0 | auto* saddr = reinterpret_cast<sockaddr*>(&addr); |
368 | 0 | size_t len = sizeof(addr); |
369 | 0 | if (connect(r, saddr, len) == -1) { |
370 | 0 | std::string emsg = "Unable to connect socket: " + std::string(strerror(errno)); |
371 | 0 | close(r); |
372 | 0 | throw std::runtime_error(emsg); |
373 | 0 | } |
374 | 0 |
|
375 | 0 | fd_set wd; |
376 | 0 | FD_ZERO(&wd); |
377 | 0 | FD_SET(r, &wd); |
378 | 0 | if (select(r + 1, nullptr, &wd, nullptr, nullptr) == -1) { |
379 | 0 | std::string emsg = "Failed to connect socket while waiting for writeability: " + std::string(strerror(errno)); |
380 | 0 | close(r); |
381 | 0 | throw std::runtime_error(emsg); |
382 | 0 | } |
383 | 0 |
|
384 | 0 | auto version = HSTORE_VERSION; |
385 | 0 | mqwrite(r, reinterpret_cast<const uint8_t*>(&version), sizeof(version)); |
386 | 0 | return r; |
387 | 0 | } |
388 | | |
389 | 0 | inline int mqlisten(const std::string& fileName) { |
390 | 0 | int s = socket(AF_UNIX, SOCK_STREAM, 0); |
391 | 0 | if (s == -1) { |
392 | 0 | throw std::runtime_error("Unable to allocate socket: " + std::string(strerror(errno))); |
393 | 0 | } |
394 | 0 |
|
395 | 0 | sockaddr_un addr; |
396 | 0 | if (fileName.size() > sizeof(addr.sun_path) - 1) { |
397 | 0 | throw std::runtime_error("Domain socket pathname too long: " + fileName); |
398 | 0 | } |
399 | 0 |
|
400 | 0 | memset(&addr, 0, sizeof(addr)); |
401 | 0 | addr.sun_family = AF_UNIX; |
402 | 0 | unlink(fileName.c_str()); |
403 | 0 | snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", fileName.c_str()); |
404 | 0 |
|
405 | 0 | if (bind(s, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) { |
406 | 0 | std::string emsg = "Unable to bind socket to file: " + fileName + " (" + std::string(strerror(errno)) + ")"; |
407 | 0 | close(s); |
408 | 0 | throw std::runtime_error(emsg); |
409 | 0 | } |
410 | 0 |
|
411 | 0 | // and then start to listen |
412 | 0 | if (listen(s, SOMAXCONN) == -1) { |
413 | 0 | std::string emsg = "Unable to listen socket on file: " + fileName + " (" + std::string(strerror(errno)) + ")"; |
414 | 0 | close(s); |
415 | 0 | throw std::runtime_error(emsg); |
416 | 0 | } |
417 | 0 |
|
418 | 0 | return s; |
419 | 0 | } |
420 | | |
421 | 0 | inline std::string defaultStoreDir() { |
422 | 0 | const char* td = nullptr; |
423 | 0 | if ((td = ::getenv("HOBBES_STORE_DIR")) != nullptr) { |
424 | 0 | if (::strlen(td) > 0 && ::access(td, W_OK) == 0) { |
425 | 0 | return std::string(td); |
426 | 0 | } |
427 | 0 | } |
428 | 0 | if ((td = ::getenv("TMPDIR")) != nullptr) { |
429 | 0 | if (::strlen(td) > 0 && ::access(td, W_OK) == 0) { |
430 | 0 | return std::string(td); |
431 | 0 | } |
432 | 0 | } |
433 | 0 | if ((td = ::getenv("TMP")) != nullptr) { |
434 | 0 | if (::strlen(td) > 0 && ::access(td, W_OK) == 0) { |
435 | 0 | return std::string(td); |
436 | 0 | } |
437 | 0 | } |
438 | 0 | if (::access("/var/tmp", W_OK) == 0) { |
439 | 0 | return "/var/tmp"; |
440 | 0 | } |
441 | 0 | return "/tmp"; |
442 | 0 | } |
443 | | |
444 | 0 | inline int connectGroupHost(const std::string& groupName, const std::string& sdir = defaultStoreDir()) { |
445 | 0 | auto sp = sdir + "/hstore." + groupName + ".sk"; |
446 | 0 | try { |
447 | 0 | return mqconnect(sp); |
448 | 0 | } catch (std::exception& e) { |
449 | 0 | throw std::runtime_error("Failed to connect to log consumer for group '" + groupName + "' on socket '" + sp + "' (" + std::string(e.what()) + ")"); |
450 | 0 | } |
451 | 0 | } |
452 | | |
453 | 0 | inline int makeGroupHost(const std::string& groupName, const std::string& sdir = defaultStoreDir()) { |
454 | 0 | auto sp = sdir + "/hstore." + groupName + ".sk"; |
455 | 0 | try { |
456 | 0 | return mqlisten(sp); |
457 | 0 | } catch (std::exception& e) { |
458 | 0 | throw std::runtime_error("Failed to listen for log connections for group '" + groupName + "' on socket '" + sp + "' (" + std::string(e.what()) + ")"); |
459 | 0 | } |
460 | 0 | } |
461 | | |
462 | | // identify this process/thread |
463 | | using ProcThread = std::pair<uint64_t, uint64_t>; |
464 | | |
465 | 0 | inline ProcThread thisProcThread() { |
466 | 0 | ProcThread r; |
467 | 0 | r.first = static_cast<uint64_t>(getpid()); |
468 | 0 | #if defined(__APPLE__) && defined(__MACH__) |
469 | 0 | pthread_threadid_np(0, &r.second); |
470 | 0 | #else |
471 | 0 | r.second = static_cast<uint64_t>(syscall(SYS_gettid)); |
472 | 0 | #endif |
473 | 0 | return r; |
474 | 0 | } |
475 | | |
476 | | // derive a name for a shared memory region with this group name in this thread/process |
477 | 0 | inline std::string sharedMemName(const std::string& groupName, const ProcThread& pt) { |
478 | 0 | std::ostringstream ss; |
479 | 0 | ss << "/" << groupName << "." << pt.first << "." << pt.second; |
480 | 0 | return ss.str(); |
481 | 0 | } |
482 | 0 | inline std::string sharedMemName(const std::string& groupName) { |
483 | 0 | return sharedMemName(groupName, thisProcThread()); |
484 | 0 | } |
485 | | |
486 | | // register the allocation of a shared memory region with this group name in this thread/process |
487 | 0 | inline void registerSHMAlloc(int* mqserver, const char* groupName, const WaitPolicy wp, bool reconnect = false, ProcThread pt = thisProcThread()) { |
488 | 0 | if (*mqserver < 0) { |
489 | 0 | *mqserver = connectGroupHost(groupName); |
490 | 0 | } |
491 | 0 |
|
492 | 0 | uint8_t msg[1+sizeof(ProcThread)] = {0}; |
493 | 0 | uint8_t& cmd = msg[0]; |
494 | 0 | cmd |= reconnect ? 1 : 0; |
495 | 0 | cmd |= static_cast<uint8_t>(wp) << 1; |
496 | 0 | memcpy(msg+1, &pt, sizeof(ProcThread)); |
497 | 0 | mqwrite(*mqserver, msg, sizeof(msg)); |
498 | 0 | } |
499 | | |
500 | 0 | inline void reconnectSHM(int* mqserver, const char* groupName, const WaitPolicy wp, const std::vector<ProcThread>& pts) { |
501 | 0 | // has the other side disconnected? |
502 | 0 | if (*mqserver >= 0) { |
503 | 0 | fd_set rd; |
504 | 0 | FD_ZERO(&rd); |
505 | 0 | FD_SET(*mqserver, &rd); |
506 | 0 |
|
507 | 0 | timeval tmout; |
508 | 0 | tmout.tv_sec = 0; |
509 | 0 | tmout.tv_usec = 1; |
510 | 0 |
|
511 | 0 | auto sr = select(*mqserver+1, &rd, nullptr, nullptr, &tmout); |
512 | 0 | if (sr == 1) { |
513 | 0 | char b=0; |
514 | 0 | if (recv(*mqserver, &b, 1, MSG_PEEK) == 0) { |
515 | 0 | close(*mqserver); |
516 | 0 | *mqserver = -1; |
517 | 0 | } |
518 | 0 | } |
519 | 0 | } |
520 | 0 |
|
521 | 0 | // if we're in a disconnected state, try to reconnect and re-register all buffers |
522 | 0 | if (*mqserver < 0) { |
523 | 0 | try { |
524 | 0 | for (const auto& pt : pts) { |
525 | 0 | registerSHMAlloc(mqserver, groupName, wp, true, pt); |
526 | 0 | } |
527 | 0 | } catch (std::exception& ex) { |
528 | 0 | } |
529 | 0 | } |
530 | 0 | } |
531 | | |
532 | | // between queue readers and writers, there are only three states of concern: |
533 | | // 0: both reader and writer are making progress |
534 | | // 1: the reader is blocked waiting for new values (empty queue) |
535 | | // 2: the writer is blocked waiting for the reader to catch up (full queue) |
536 | | #define PRIV_HSTORE_STATE_UNBLOCKED 0 |
537 | | #define PRIV_HSTORE_STATE_READER_WAITING 1 |
538 | | #define PRIV_HSTORE_STATE_WRITER_WAITING 2 |
539 | | |
540 | | struct pqueue_config { |
541 | | pqueue_config() : |
542 | 0 | valuesz(0), count(0), wstate(nullptr), readerIndex(nullptr), writerIndex(nullptr), data(nullptr) { |
543 | 0 | } |
544 | | |
545 | | pqueue_config(size_t valuesz, size_t count, uint32_t* wstate, uint32_t* ri, uint32_t* wi, uint8_t* data) : |
546 | | valuesz(valuesz), count(count), wstate(wstate), readerIndex(ri), writerIndex(wi), data(data) |
547 | 0 | { |
548 | 0 | } |
549 | | |
550 | | size_t valuesz; // how large is one "value" or queue element? |
551 | | size_t count; // how many "values" (of size 'valuesz') are there indexable from 'data'? |
552 | | volatile uint32_t* wstate; // inter-process wait state : 0=no waiting, 1=reader waiting, 2=writer waiting |
553 | | volatile uint32_t* readerIndex; // where is the reader in the data sequence? |
554 | | volatile uint32_t* writerIndex; // where is the writer in the data sequence? |
555 | | uint8_t* data; // the actual queue data |
556 | | }; |
557 | | |
558 | | // shared memory queue data |
559 | | struct ShQueueHeader { |
560 | | uint32_t ready; // set to 1 when the queue has been fully constructed and is ready to read |
561 | | size_t valsz; // the size of a single "queue value" |
562 | | size_t count; // the number of queue values defined in the queue |
563 | | size_t metasz; // the size of the following meta-data section |
564 | | }; |
565 | | |
566 | | struct ShQueueData { |
567 | | uint32_t wstate; |
568 | | uint32_t ri; |
569 | | uint32_t wi; |
570 | | uint32_t unused; |
571 | | }; |
572 | | |
573 | | // write data into shared memory |
574 | | class writer { |
575 | | private: |
576 | | std::string shmname; |
577 | | int shmfd; |
578 | | pqueue_config cfg; |
579 | | WaitFn waitFn; |
580 | | WakeFn wakeFn; |
581 | | |
582 | | // the writer's own copy of its position in the queue. Only the writer ever |
583 | | // advances the write index, so it is kept here and published to the segment |
584 | | // on push rather than re-read from the wi word each time. That word lives in |
585 | | // the shared mapping, which the peer -- and, with the segment's 0666 |
586 | | // permissions, any local user -- can overwrite with any value; deriving the |
587 | | // memcpy target from it let a poisoned wi steer the producer's own write far |
588 | | // outside the queue (STRFR-434014). A private copy the peer cannot reach is |
589 | | // the reader-side index check's counterpart for the producer. |
590 | | uint32_t wpos = 0; |
591 | | |
592 | 0 | inline volatile uint32_t* waitState() const { return this->cfg.wstate; } |
593 | 0 | inline volatile uint32_t* readIndex() const { return this->cfg.readerIndex; } |
594 | 0 | inline volatile uint32_t* writeIndex() const { return this->cfg.writerIndex; } |
595 | | // even though wpos is now the source of the index, a raw index still gets a |
596 | | // bounds check before it sizes a pointer -- matching the reader and guarding |
597 | | // against a future path handing value() an out-of-range index |
598 | 0 | inline uint8_t* value(size_t i) const { |
599 | 0 | if (PRIV_HSTORE_UNLIKELY(i >= this->cfg.count)) { |
600 | 0 | throw std::runtime_error("queue index " + std::to_string(i) + " out of range for a queue of " + std::to_string(this->cfg.count) + " values"); |
601 | 0 | } |
602 | 0 | return this->cfg.data + (i*this->cfg.valuesz); |
603 | 0 | } |
604 | 0 | inline uint32_t advance(uint32_t i) const { return (i + 1) % this->cfg.count; } |
605 | | public: |
606 | 0 | writer(const bytes& meta, const std::string& shmname, size_t qvalsz, size_t count, const WaitPolicy wp) : waitFn(hobbes::storage::waitFn(wp)), wakeFn(hobbes::storage::wakeFn(wp)) { |
607 | 0 | shm_unlink(shmname.c_str()); |
608 | 0 |
|
609 | 0 | // sections of shared memory should be aligned to page boundaries |
610 | 0 | long pagesz = sysconf(_SC_PAGESIZE); |
611 | 0 | if (pagesz == -1) { |
612 | 0 | throw std::runtime_error("Failed to query system page size for '" + shmname + "': " + strerror(errno)); |
613 | 0 | } |
614 | 0 | |
615 | 0 | // create the shared memory region |
616 | 0 | int shfd = shm_open(shmname.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); |
617 | 0 | if (shfd == -1) { |
618 | 0 | throw std::runtime_error("Failed to allocate shared memory for '" + shmname + "': " + strerror(errno)); |
619 | 0 | } |
620 | 0 | |
621 | 0 | // our meta-data section comes first up to the first page boundary |
622 | 0 | // then our data section comes next |
623 | 0 | auto metaLen = align<size_t>(sizeof(ShQueueHeader) + meta.size(), pagesz); |
624 | 0 | auto dataLen = align<size_t>(sizeof(ShQueueData) + qvalsz*count, pagesz); |
625 | 0 | size_t memLen = metaLen + dataLen; |
626 | 0 | |
627 | 0 | // allocate this much data |
628 | 0 | if (ftruncate(shfd, memLen) == -1) { |
629 | 0 | throw std::runtime_error("Failed to truncate shared memory for '" + shmname + "': " + strerror(errno)); |
630 | 0 | } |
631 | 0 | |
632 | 0 | auto* mem = reinterpret_cast<uint8_t*>(mmap(nullptr, memLen, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, shfd, 0)); |
633 | 0 | if (mem == MAP_FAILED) { |
634 | 0 | throw std::runtime_error("Failed to map bytes out of shared memory for '" + shmname + "': " + strerror(errno)); |
635 | 0 | } |
636 | 0 | |
637 | 0 | // write meta data |
638 | 0 | auto* hdr = reinterpret_cast<ShQueueHeader*>(mem); |
639 | 0 | hdr->valsz = qvalsz; |
640 | 0 | hdr->count = count; |
641 | 0 | hdr->metasz = meta.size(); |
642 | 0 | // a queue with no metadata is legitimate, so take the buffer with data() |
643 | 0 | // and skip the copy entirely when it is empty -- '&meta[0]' on an empty |
644 | 0 | // vector is undefined, and aborts where libstdc++ assertions are on |
645 | 0 | if (!meta.empty()) { |
646 | 0 | memcpy(mem + sizeof(ShQueueHeader), meta.data(), meta.size()); |
647 | 0 | } |
648 | 0 | |
649 | 0 | // OK, this queue is fully initialized |
650 | 0 | uxchg(&hdr->ready, 1); |
651 | 0 | |
652 | 0 | // now make this pqueue config |
653 | 0 | auto* sqd = reinterpret_cast<ShQueueData*>(mem + metaLen); |
654 | 0 | |
655 | 0 | this->shmname = shmname; |
656 | 0 | this->shmfd = shfd; |
657 | 0 | this->cfg.valuesz = qvalsz; |
658 | 0 | this->cfg.count = count; |
659 | 0 | this->cfg.wstate = &sqd->wstate; |
660 | 0 | this->cfg.readerIndex = &sqd->ri; |
661 | 0 | this->cfg.writerIndex = &sqd->wi; |
662 | 0 | this->cfg.data = mem + metaLen + sizeof(ShQueueData); |
663 | 0 | } |
664 | | |
665 | 0 | ~writer() { |
666 | 0 | shm_unlink(this->shmname.c_str()); |
667 | 0 | } |
668 | | |
669 | 0 | inline const pqueue_config& config() const { return this->cfg; } |
670 | | |
671 | 0 | uint8_t* next(size_t timeoutNS = 0, const std::function<void()>& timeoutF = [](){}) { |
672 | 0 | uint32_t nwi = advance(this->wpos); |
673 | 0 |
|
674 | 0 | unsigned count = PRIV_HSTORE_SPIN_MIN; |
675 | 0 |
|
676 | 0 | while (PRIV_HSTORE_UNLIKELY(*readIndex() == nwi)) { |
677 | 0 | if (count < PRIV_HSTORE_SPIN_MAX) { |
678 | 0 | // back-off the writer |
679 | 0 | count = spin(count); |
680 | 0 | } else { |
681 | 0 | // the reader is behind and we've caught up with it, switch into writer-wait mode |
682 | 0 | switch (xchg(waitState(), PRIV_HSTORE_STATE_WRITER_WAITING)) { |
683 | 0 | case PRIV_HSTORE_STATE_UNBLOCKED: |
684 | 0 | // we previously were unblocked |
685 | 0 | // make sure that we still need to block the writer (in case the read index moved while we were getting here) |
686 | 0 | // then block while we're in writer-wait state |
687 | 0 | if (*readIndex() == nwi) { |
688 | 0 | (*waitFn)(waitState(), PRIV_HSTORE_STATE_WRITER_WAITING, timeoutNS, timeoutF); |
689 | 0 | } |
690 | 0 | break; |
691 | 0 | case PRIV_HSTORE_STATE_READER_WAITING: |
692 | 0 | // we previously were in reader-wait state (this should practically never happen) |
693 | 0 | // since we wait to write anyway, unblock the reader and try again |
694 | 0 | uxchg(waitState(), PRIV_HSTORE_STATE_UNBLOCKED); |
695 | 0 | (*wakeFn)(waitState(), 1); |
696 | 0 | break; |
697 | 0 | } |
698 | 0 | } |
699 | 0 | } |
700 | 0 | return value(this->wpos); |
701 | 0 | } |
702 | | |
703 | 0 | uint8_t* pollNext() { |
704 | 0 | if (PRIV_HSTORE_UNLIKELY(*readIndex() == advance(this->wpos))) { |
705 | 0 | return nullptr; |
706 | 0 | } else { |
707 | 0 | return value(this->wpos); |
708 | 0 | } |
709 | 0 | } |
710 | | |
711 | 0 | void push() { |
712 | 0 | this->wpos = advance(this->wpos); |
713 | 0 | uxchg(writeIndex(), this->wpos); |
714 | 0 |
|
715 | 0 | // when the writer advances, the reader can be unblocked |
716 | 0 | if (PRIV_HSTORE_UNLIKELY(xchg(waitState(), PRIV_HSTORE_STATE_UNBLOCKED) == PRIV_HSTORE_STATE_READER_WAITING)) { |
717 | 0 | (*wakeFn)(waitState(), 1); |
718 | 0 | } |
719 | 0 | } |
720 | | }; |
721 | | |
722 | | // shared memory pages can be marked as representing four possible conditions: |
723 | | // 1) page is published but state is undetermined (contingent on being able to acquire another page without blocking) |
724 | | // 2) page is a continuation of its transaction (intermediate in the total transaction) |
725 | | // 3) page successfully terminates the transaction (commit) |
726 | | // 4) page prematurely terminates the transaction (rollback) |
727 | | #define PRIV_HSTORE_PAGE_STATE_TENTATIVE static_cast<uint8_t>(0) |
728 | | #define PRIV_HSTORE_PAGE_STATE_CONT static_cast<uint8_t>(1) |
729 | | #define PRIV_HSTORE_PAGE_STATE_COMMIT static_cast<uint8_t>(2) |
730 | | #define PRIV_HSTORE_PAGE_STATE_ROLLBACK static_cast<uint8_t>(3) |
731 | | |
732 | | // writers may record data 'unreliably' (non-blocking) or 'reliably' (block iff waiting for consumer to catch up) |
733 | | enum PipeQOS { |
734 | | Reliable = 0, |
735 | | Unreliable |
736 | | }; |
737 | | |
738 | | // wpipe : a "write pipe" (to write an 'arbitrary-length' sequence of data) on top of writers |
739 | | class wpipe { |
740 | | private: |
741 | | writer* wq; |
742 | | uint8_t* page; |
743 | | size_t pagesz; |
744 | | uint32_t offset; |
745 | | PipeQOS qos; |
746 | | size_t timeoutNS; |
747 | | std::function<void()> timeoutF; |
748 | | size_t unrtimeNS; |
749 | | |
750 | 0 | void markPage(uint8_t c) { |
751 | 0 | *reinterpret_cast<uint32_t*>(this->page + this->pagesz) = (static_cast<uint32_t>(c) << 24) | this->offset; |
752 | 0 | } |
753 | | |
754 | 0 | inline bool reliable() const { |
755 | 0 | return this->qos == Reliable; |
756 | 0 | } |
757 | | |
758 | 0 | bool stepPage() { |
759 | 0 | if (reliable()) { |
760 | 0 | markPage(PRIV_HSTORE_PAGE_STATE_CONT); |
761 | 0 | this->wq->push(); |
762 | 0 |
|
763 | 0 | this->page = this->wq->next(this->timeoutNS, this->timeoutF); |
764 | 0 | this->offset = 0; |
765 | 0 | return true; |
766 | 0 | } else { |
767 | 0 | markPage(PRIV_HSTORE_PAGE_STATE_TENTATIVE); |
768 | 0 | this->wq->push(); |
769 | 0 | uint8_t* npage = this->wq->pollNext(); |
770 | 0 | markPage(npage != nullptr ? PRIV_HSTORE_PAGE_STATE_CONT : PRIV_HSTORE_PAGE_STATE_ROLLBACK); |
771 | 0 |
|
772 | 0 | this->page = npage; |
773 | 0 | this->offset = 0; |
774 | 0 | return npage != nullptr; |
775 | 0 | } |
776 | 0 | } |
777 | | public: |
778 | 0 | wpipe(writer* wq, PipeQOS qos = Reliable, size_t timeoutNS = 0, const std::function<void()>& timeoutF = [](){}) : wq(wq), pagesz(wq->config().valuesz - sizeof(uint32_t)), offset(0), qos(qos), timeoutNS(timeoutNS), timeoutF(timeoutF), unrtimeNS(0) { |
779 | 0 | if (wq->config().valuesz <= sizeof(uint32_t)) { |
780 | 0 | throw std::runtime_error("queue page size too small for use as shared memory pipe"); |
781 | 0 | } |
782 | 0 | this->page = wq->pollNext(); |
783 | 0 | } |
784 | | |
785 | 0 | void commit() { |
786 | 0 | if (reliable()) { |
787 | 0 | markPage(PRIV_HSTORE_PAGE_STATE_COMMIT); |
788 | 0 | this->wq->push(); |
789 | 0 | this->page = this->wq->next(this->timeoutNS, this->timeoutF); |
790 | 0 | } else { |
791 | 0 | if (PRIV_HSTORE_LIKELY(this->page != nullptr)) { |
792 | 0 | markPage(PRIV_HSTORE_PAGE_STATE_COMMIT); |
793 | 0 | this->wq->push(); |
794 | 0 | } |
795 | 0 | this->page = this->wq->pollNext(); |
796 | 0 |
|
797 | 0 | // allow unreliable producers to process timeout events |
798 | 0 | if (PRIV_HSTORE_UNLIKELY(this->page == nullptr)) { |
799 | 0 | if (this->unrtimeNS == 0) { |
800 | 0 | this->unrtimeNS = internal::spin::poll_tickNS(); |
801 | 0 | } else if (internal::spin::poll_tickNS() - this->unrtimeNS >= this->timeoutNS) { |
802 | 0 | this->timeoutF(); |
803 | 0 | this->unrtimeNS = 0; |
804 | 0 | } |
805 | 0 | } |
806 | 0 | } |
807 | 0 | this->offset = 0; |
808 | 0 | } |
809 | | |
810 | 0 | void rollback() { |
811 | 0 | if (PRIV_HSTORE_LIKELY(this->page != nullptr)) { |
812 | 0 | markPage(PRIV_HSTORE_PAGE_STATE_ROLLBACK); |
813 | 0 | this->wq->push(); |
814 | 0 | } |
815 | 0 | this->page = reliable() ? this->wq->next(this->timeoutNS, this->timeoutF) : this->wq->pollNext(); |
816 | 0 | this->offset = 0; |
817 | 0 | } |
818 | | |
819 | 0 | bool hasSpaceFor(size_t sz) const { |
820 | 0 | return (this->page != nullptr) && sz < (this->pagesz - this->offset); |
821 | 0 | } |
822 | | |
823 | | // write a block of bytes within a frame |
824 | 0 | bool write(const uint8_t* src, size_t sz) { |
825 | 0 | // just for unreliable pipes, we might enter here without a page |
826 | 0 | if (PRIV_HSTORE_UNLIKELY(!this->page)) { |
827 | 0 | return false; |
828 | 0 | } |
829 | 0 |
|
830 | 0 | size_t remsz = this->pagesz - this->offset; |
831 | 0 |
|
832 | 0 | // most of the time we'll be writing small chunks of data |
833 | 0 | if (PRIV_HSTORE_LIKELY(sz < remsz)) { |
834 | 0 | memcpy(this->page + this->offset, src, sz); |
835 | 0 | this->offset += sz; |
836 | 0 | return true; |
837 | 0 | } |
838 | 0 |
|
839 | 0 | // now we might cross any number of pages |
840 | 0 | // write the src prefix to the remaining page space |
841 | 0 | // write all intermediate complete pages |
842 | 0 | // write the src suffix to the last page |
843 | 0 | memcpy(this->page + this->offset, src, remsz); |
844 | 0 | this->offset += remsz; |
845 | 0 |
|
846 | 0 | size_t so = remsz; |
847 | 0 | while (stepPage() && sz - so >= this->pagesz) { |
848 | 0 | memcpy(this->page, src + so, this->pagesz); |
849 | 0 | this->offset = this->pagesz; |
850 | 0 | so += this->pagesz; |
851 | 0 | } |
852 | 0 | if (this->page == nullptr) { |
853 | 0 | return false; |
854 | 0 | } else { |
855 | 0 | if (so < sz) { |
856 | 0 | this->offset = sz - so; |
857 | 0 | memcpy(this->page, src + so, this->offset); |
858 | 0 | } |
859 | 0 | return true; |
860 | 0 | } |
861 | 0 | } |
862 | | }; |
863 | | |
864 | | struct QueueConnection { |
865 | | int shfd; |
866 | | uint8_t* data; |
867 | | size_t datasz; |
868 | | size_t pagesz; |
869 | | std::string shmname; |
870 | | }; |
871 | | |
872 | 0 | inline QueueConnection consumeQueue(const std::string& shmname) { |
873 | 0 | // sections of shared memory are aligned to page boundaries |
874 | 0 | long pagesz = sysconf(_SC_PAGESIZE); |
875 | 0 | if (pagesz == -1) { |
876 | 0 | throw std::runtime_error("Failed to query system page size for '" + shmname + "': " + strerror(errno)); |
877 | 0 | } |
878 | 0 |
|
879 | 0 | // see if we can open this shared memory region |
880 | 0 | int shfd = shm_open(shmname.c_str(), O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); |
881 | 0 | if (shfd == -1) { |
882 | 0 | throw std::runtime_error("Failed to open shared memory for '" + shmname + "': " + strerror(errno)); |
883 | 0 | } |
884 | 0 |
|
885 | 0 | // make sure that the region has been sized |
886 | 0 | struct stat msb; |
887 | 0 | if (fstat(shfd, &msb) < 0) { |
888 | 0 | close(shfd); |
889 | 0 | throw std::runtime_error("Failed to stat shared memory for '" + shmname + "': " + strerror(errno)); |
890 | 0 | } |
891 | 0 | if (msb.st_size <= 0) { |
892 | 0 | close(shfd); |
893 | 0 | throw std::runtime_error("Shared memory for '" + shmname + "' is not ready"); |
894 | 0 | } |
895 | 0 |
|
896 | 0 | // map memory for this data, ensure it is in a good state |
897 | 0 | auto* mem = reinterpret_cast<uint8_t*>(mmap(nullptr, msb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, shfd, 0)); |
898 | 0 | if (mem == MAP_FAILED) { |
899 | 0 | close(shfd); |
900 | 0 | throw std::runtime_error("Failed to map bytes out of shared memory for '" + shmname + "': " + strerror(errno)); |
901 | 0 | } |
902 | 0 |
|
903 | 0 | if (reinterpret_cast<ShQueueHeader*>(mem)->ready == 0u) { |
904 | 0 | munmap(mem, msb.st_size); |
905 | 0 | close(shfd); |
906 | 0 | throw std::runtime_error("Not ready to consume shared memory for '" + shmname + "'"); |
907 | 0 | } |
908 | 0 |
|
909 | 0 | QueueConnection c; |
910 | 0 | c.shfd = shfd; |
911 | 0 | c.data = mem; |
912 | 0 | c.datasz = msb.st_size; |
913 | 0 | c.pagesz = pagesz; |
914 | 0 | c.shmname = shmname; |
915 | 0 | return c; |
916 | 0 | } |
917 | | |
918 | 0 | inline QueueConnection consumeGroup(const std::string& gname, const ProcThread& pt) { |
919 | 0 | return consumeQueue(sharedMemName(gname, pt)); |
920 | 0 | } |
921 | | |
922 | | // a queue segment's header says where everything else in it is, and the |
923 | | // writer that filled it in shares the segment with this reader: hog's group |
924 | | // socket lets any process that can reach it hand over a segment, and one |
925 | | // producer must not be able to take down a collector serving every other |
926 | | // group. Hold the header to the segment it describes before any of it is |
927 | | // used as a position -- metasz placed the control block and the data outside |
928 | | // the mapping (a fault on the first poll), and a count of zero made |
929 | | // nextIndex divide by zero. |
930 | 0 | [[noreturn]] inline void badQueueHeader(const QueueConnection& qc, const std::string& why) { |
931 | 0 | throw std::runtime_error("Invalid queue header in '" + qc.shmname + "': " + why); |
932 | 0 | } |
933 | | |
934 | 0 | inline void checkQueueHeader(const QueueConnection& qc, const ShQueueHeader* hdr) { |
935 | 0 | if (qc.datasz < sizeof(ShQueueHeader)) { |
936 | 0 | badQueueHeader(qc, "segment is too small to hold a queue header"); |
937 | 0 | } |
938 | 0 | if (hdr->metasz > qc.datasz - sizeof(ShQueueHeader)) { |
939 | 0 | badQueueHeader(qc, "meta-data section runs past the end of the segment"); |
940 | 0 | } |
941 | 0 |
|
942 | 0 | size_t metaLen = align<size_t>(sizeof(ShQueueHeader) + hdr->metasz, qc.pagesz); |
943 | 0 | if (metaLen > qc.datasz || qc.datasz - metaLen < sizeof(ShQueueData)) { |
944 | 0 | badQueueHeader(qc, "queue control block runs past the end of the segment"); |
945 | 0 | } |
946 | 0 |
|
947 | 0 | size_t avail = qc.datasz - metaLen - sizeof(ShQueueData); |
948 | 0 | if (hdr->count == 0) { |
949 | 0 | badQueueHeader(qc, "queue holds no values"); |
950 | 0 | } |
951 | 0 | if (hdr->valsz == 0 || hdr->valsz > avail / hdr->count) { |
952 | 0 | badQueueHeader(qc, "queue values run past the end of the segment"); |
953 | 0 | } |
954 | 0 | } |
955 | | |
956 | | // read data out of shared memory |
957 | | class reader { |
958 | | private: |
959 | | int shfd; |
960 | | const uint8_t* metad; |
961 | | size_t metasz; |
962 | | pqueue_config cfg; |
963 | | WaitFn waitFn; |
964 | | WakeFn wakeFn; |
965 | | |
966 | 0 | inline volatile uint32_t* waitState() const { return this->cfg.wstate; } |
967 | 0 | inline volatile uint32_t* readIndex() const { return this->cfg.readerIndex; } |
968 | 0 | inline volatile uint32_t* writeIndex() const { return this->cfg.writerIndex; } |
969 | | // the read and write indexes live in the segment, where the writer can put |
970 | | // anything in them, and they are otherwise only compared with each other -- |
971 | | // an index past the end of the queue addressed memory outside the mapping. |
972 | | // One predictable comparison here is nothing beside the shared-memory |
973 | | // traffic this sits on. |
974 | 0 | inline uint8_t* value(size_t i) const { |
975 | 0 | if (PRIV_HSTORE_UNLIKELY(i >= this->cfg.count)) { |
976 | 0 | throw std::runtime_error("queue index " + std::to_string(i) + " out of range for a queue of " + std::to_string(this->cfg.count) + " values"); |
977 | 0 | } |
978 | 0 | return this->cfg.data + (i*this->cfg.valuesz); |
979 | 0 | } |
980 | 0 | inline uint32_t nextIndex(volatile uint32_t* i) const { return (*i + 1) % this->cfg.count; } |
981 | | public: |
982 | 0 | reader(const QueueConnection& qc, const WaitPolicy wp) : shfd(qc.shfd), waitFn(hobbes::storage::waitFn(wp)), wakeFn(hobbes::storage::wakeFn(wp)) { |
983 | 0 | // prepare to read the queue description |
984 | 0 | auto* hdr = reinterpret_cast<ShQueueHeader*>(qc.data); |
985 | 0 | checkQueueHeader(qc, hdr); |
986 | 0 | auto metaLen = align<size_t>(sizeof(ShQueueHeader) + hdr->metasz, qc.pagesz); |
987 | 0 | auto* sqd = reinterpret_cast<ShQueueData*>(qc.data + metaLen); |
988 | 0 |
|
989 | 0 | // now we should have enough to read out of this queue |
990 | 0 | this->metad = qc.data + sizeof(ShQueueHeader); |
991 | 0 | this->metasz = hdr->metasz; |
992 | 0 | this->cfg.valuesz = hdr->valsz; |
993 | 0 | this->cfg.count = hdr->count; |
994 | 0 | this->cfg.wstate = &sqd->wstate; |
995 | 0 | this->cfg.readerIndex = &sqd->ri; |
996 | 0 | this->cfg.writerIndex = &sqd->wi; |
997 | 0 | this->cfg.data = qc.data + metaLen + sizeof(ShQueueData); |
998 | 0 | } |
999 | | |
1000 | 0 | ~reader() { |
1001 | 0 | close(this->shfd); |
1002 | 0 | } |
1003 | | |
1004 | 0 | inline const pqueue_config& config() const { return this->cfg; } |
1005 | | |
1006 | | // access queue init data; (null,0) if no data was specified |
1007 | | using MetaData = std::pair<const uint8_t *, size_t>; |
1008 | 0 | MetaData meta() const { |
1009 | 0 | return MetaData(this->metad, this->metasz); |
1010 | 0 | } |
1011 | | |
1012 | | // get the next value in the queue, blocking if necessary |
1013 | 0 | uint8_t* next(size_t timeoutNS, const std::function<void()>& timeoutF) { |
1014 | 0 | uint32_t ri = *readIndex(); |
1015 | 0 |
|
1016 | 0 | unsigned count = PRIV_HSTORE_SPIN_MIN; |
1017 | 0 | |
1018 | 0 | while (PRIV_HSTORE_UNLIKELY(*writeIndex() == ri)) { |
1019 | 0 | if (count < PRIV_HSTORE_SPIN_MAX) { |
1020 | 0 | // try back-off the reader |
1021 | 0 | count = spin(count); |
1022 | 0 | } else { |
1023 | 0 | // there's nothing to read, switch into reader-wait mode |
1024 | 0 | switch (xchg(waitState(), PRIV_HSTORE_STATE_READER_WAITING)) { |
1025 | 0 | case PRIV_HSTORE_STATE_READER_WAITING: |
1026 | 0 | // for some reason (e.g. resuming on a shm block which was being operated |
1027 | 0 | // upon by an writer/reader instance that unnaturally terminated) |
1028 | 0 | // this reader is in this state but not waiting as in the UNBLOCKED |
1029 | 0 | // case. Just do UNBLOCKED case to resolve the potential infinite loop. |
1030 | 0 | case PRIV_HSTORE_STATE_UNBLOCKED: |
1031 | 0 | // we previously were unblocked |
1032 | 0 | // make sure that we still need to block the reader (in case the write index moved while we were getting here) |
1033 | 0 | // then block while we're in reader-wait state |
1034 | 0 | if (*writeIndex() == ri) { |
1035 | 0 | (*waitFn)(waitState(), PRIV_HSTORE_STATE_READER_WAITING, timeoutNS, timeoutF); |
1036 | 0 | } |
1037 | 0 | break; |
1038 | 0 | case PRIV_HSTORE_STATE_WRITER_WAITING: |
1039 | 0 | // we previously were in writer-wait state (this should practically never happen) |
1040 | 0 | // since we wait to read anyway, unblock the writer and try again |
1041 | 0 | uxchg(waitState(), PRIV_HSTORE_STATE_UNBLOCKED); |
1042 | 0 | (*wakeFn)(waitState(), 1); |
1043 | 0 | break; |
1044 | 0 | } |
1045 | 0 | } |
1046 | 0 | } |
1047 | 0 | return value(*readIndex()); |
1048 | 0 | } |
1049 | | |
1050 | | // get the next value in the queue if one is present, else null |
1051 | 0 | uint8_t* pollNext() { |
1052 | 0 | if (PRIV_HSTORE_UNLIKELY(*writeIndex() == *readIndex())) { |
1053 | 0 | return nullptr; |
1054 | 0 | } else { |
1055 | 0 | return value(*readIndex()); |
1056 | 0 | } |
1057 | 0 | } |
1058 | | |
1059 | | // remove the next value from the queue (increment the read index) |
1060 | 0 | void pop() { |
1061 | 0 | uxchg(readIndex(), nextIndex(readIndex())); |
1062 | 0 | |
1063 | 0 | // when the reader advances, the writer can be unblocked |
1064 | 0 | if (PRIV_HSTORE_UNLIKELY(xchg(waitState(), PRIV_HSTORE_STATE_UNBLOCKED) == PRIV_HSTORE_STATE_WRITER_WAITING)) { |
1065 | 0 | (*wakeFn)(waitState(), 1); |
1066 | 0 | } |
1067 | 0 | } |
1068 | | }; |
1069 | | |
1070 | | // rpipe : a "read pipe" on top of readers |
1071 | | class rpipe { |
1072 | | private: |
1073 | | reader* rq; |
1074 | | const uint8_t* page; |
1075 | | size_t pagesz; |
1076 | | uint32_t offset; |
1077 | | public: |
1078 | 0 | rpipe(reader* rq) : rq(rq), page(nullptr), pagesz(rq->config().valuesz - sizeof(uint32_t)), offset(0) { |
1079 | 0 | if (rq->config().valuesz <= sizeof(uint32_t)) { |
1080 | 0 | throw std::runtime_error("queue page size too small for use as shared memory pipe"); |
1081 | 0 | } |
1082 | 0 | } |
1083 | | |
1084 | | // read a range of bytes out of the 'pipe' into a user-supplied buffer |
1085 | 0 | size_t read(uint8_t* dst, size_t sz, uint8_t* state, size_t timeoutNS, const std::function<void()>& timeoutF) { |
1086 | 0 | if (this->page == nullptr) { |
1087 | 0 | this->page = this->rq->next(timeoutNS, timeoutF); |
1088 | 0 | } |
1089 | 0 | |
1090 | 0 | size_t doff = 0; |
1091 | 0 | |
1092 | 0 | while (sz != 0 && (this->page != nullptr)) { |
1093 | 0 | // how much can we copy out of the current page into the dest buffer? |
1094 | 0 | const volatile uint32_t* pend = reinterpret_cast<const uint32_t*>(this->page + this->pagesz); |
1095 | 0 | size_t rpagesz = *pend & ~(0xFF << 24); |
1096 | 0 |
|
1097 | 0 | // the low 24 bits of a page's end word say how much of the page the |
1098 | 0 | // writer filled, and they came from the writer: a page of 64 bytes |
1099 | 0 | // claiming 0xFFFFFF of them copied ~16MB out of the mapping, and a |
1100 | 0 | // count behind the read offset wrapped 'rpagesz - offset' into a copy |
1101 | 0 | // of nearly the whole address space |
1102 | 0 | if (PRIV_HSTORE_UNLIKELY(rpagesz > this->pagesz || rpagesz < this->offset)) { |
1103 | 0 | throw std::runtime_error("queue page claims " + std::to_string(rpagesz) + " bytes of a " + std::to_string(this->pagesz) + " byte page (read offset " + std::to_string(this->offset) + ")"); |
1104 | 0 | } |
1105 | 0 |
|
1106 | 0 | size_t availPage = rpagesz - this->offset; |
1107 | 0 | size_t csz = (sz < availPage) ? sz : availPage; |
1108 | 0 | |
1109 | 0 | // copy as much as we can |
1110 | 0 | memcpy(dst + doff, this->page + this->offset, csz); |
1111 | 0 | |
1112 | 0 | // step through this segment we've copied |
1113 | 0 | this->offset += csz; |
1114 | 0 | doff += csz; |
1115 | 0 | sz -= csz; |
1116 | 0 | |
1117 | 0 | // if we've hit the end of the page, mark it done and then try for the next one |
1118 | 0 | if (this->offset == rpagesz) { |
1119 | 0 | // wait for the producer to decide what state the page is in |
1120 | 0 | while ((*pend >> 24) == PRIV_HSTORE_PAGE_STATE_TENTATIVE); |
1121 | 0 | uint8_t ps = *pend >> 24; |
1122 | 0 | if (state != nullptr) *state = ps; |
1123 | 0 |
|
1124 | 0 | // if this page is just a continuation, continue reading |
1125 | 0 | // else we've terminated a transaction |
1126 | 0 | this->rq->pop(); |
1127 | 0 | this->page = (ps == PRIV_HSTORE_PAGE_STATE_CONT) ? this->rq->pollNext() : nullptr; |
1128 | 0 | this->offset = 0; |
1129 | 0 | } |
1130 | 0 | } |
1131 | 0 | |
1132 | 0 | return doff; |
1133 | 0 | } |
1134 | | }; |
1135 | | |
1136 | | /***************************** |
1137 | | * |
1138 | | * store<T> : the main interface for type-directed storage into shared memory pipes |
1139 | | * |
1140 | | *****************************/ |
1141 | | template <typename T, typename P = void> |
1142 | | struct store { |
1143 | | }; |
1144 | | |
1145 | | template <typename T> struct store<const T*> : store<T*> {}; |
1146 | | |
1147 | | #define PRIV_HSTORE_DEFINE_PRIMTYS(T, n) \ |
1148 | | template <> \ |
1149 | | struct store<T> { \ |
1150 | | static const bool can_memcpy = true; \ |
1151 | 0 | static ty::desc type() { return ty::prim(n); } \Unexecuted instantiation: hobbes::storage::store<bool, void>::type() Unexecuted instantiation: hobbes::storage::store<unsigned char, void>::type() Unexecuted instantiation: hobbes::storage::store<char, void>::type() Unexecuted instantiation: hobbes::storage::store<short, void>::type() Unexecuted instantiation: hobbes::storage::store<unsigned short, void>::type() Unexecuted instantiation: hobbes::storage::store<int, void>::type() Unexecuted instantiation: hobbes::storage::store<unsigned int, void>::type() Unexecuted instantiation: hobbes::storage::store<long, void>::type() Unexecuted instantiation: hobbes::storage::store<unsigned long, void>::type() Unexecuted instantiation: hobbes::storage::store<__int128, void>::type() Unexecuted instantiation: hobbes::storage::store<float, void>::type() Unexecuted instantiation: hobbes::storage::store<double, void>::type() |
1152 | 0 | static size_t size(const T&) { return sizeof(T); } \Unexecuted instantiation: hobbes::storage::store<bool, void>::size(bool const&) Unexecuted instantiation: hobbes::storage::store<unsigned char, void>::size(unsigned char const&) Unexecuted instantiation: hobbes::storage::store<char, void>::size(char const&) Unexecuted instantiation: hobbes::storage::store<short, void>::size(short const&) Unexecuted instantiation: hobbes::storage::store<unsigned short, void>::size(unsigned short const&) Unexecuted instantiation: hobbes::storage::store<int, void>::size(int const&) Unexecuted instantiation: hobbes::storage::store<unsigned int, void>::size(unsigned int const&) Unexecuted instantiation: hobbes::storage::store<long, void>::size(long const&) Unexecuted instantiation: hobbes::storage::store<unsigned long, void>::size(unsigned long const&) Unexecuted instantiation: hobbes::storage::store<__int128, void>::size(__int128 const&) Unexecuted instantiation: hobbes::storage::store<float, void>::size(float const&) Unexecuted instantiation: hobbes::storage::store<double, void>::size(double const&) |
1153 | 0 | static bool write(wpipe& p, const T& x) { return p.write(reinterpret_cast<const uint8_t*>(&x), sizeof(x)); } \Unexecuted instantiation: hobbes::storage::store<bool, void>::write(hobbes::storage::wpipe&, bool const&) Unexecuted instantiation: hobbes::storage::store<unsigned char, void>::write(hobbes::storage::wpipe&, unsigned char const&) Unexecuted instantiation: hobbes::storage::store<char, void>::write(hobbes::storage::wpipe&, char const&) Unexecuted instantiation: hobbes::storage::store<short, void>::write(hobbes::storage::wpipe&, short const&) Unexecuted instantiation: hobbes::storage::store<unsigned short, void>::write(hobbes::storage::wpipe&, unsigned short const&) Unexecuted instantiation: hobbes::storage::store<int, void>::write(hobbes::storage::wpipe&, int const&) Unexecuted instantiation: hobbes::storage::store<unsigned int, void>::write(hobbes::storage::wpipe&, unsigned int const&) Unexecuted instantiation: hobbes::storage::store<long, void>::write(hobbes::storage::wpipe&, long const&) Unexecuted instantiation: hobbes::storage::store<unsigned long, void>::write(hobbes::storage::wpipe&, unsigned long const&) Unexecuted instantiation: hobbes::storage::store<__int128, void>::write(hobbes::storage::wpipe&, __int128 const&) Unexecuted instantiation: hobbes::storage::store<float, void>::write(hobbes::storage::wpipe&, float const&) Unexecuted instantiation: hobbes::storage::store<double, void>::write(hobbes::storage::wpipe&, double const&) |
1154 | | } |
1155 | | |
1156 | | PRIV_HSTORE_DEFINE_PRIMTYS(bool, "bool"); |
1157 | | PRIV_HSTORE_DEFINE_PRIMTYS(uint8_t, "byte"); |
1158 | | PRIV_HSTORE_DEFINE_PRIMTYS(char, "char"); |
1159 | | PRIV_HSTORE_DEFINE_PRIMTYS(int16_t, "short"); |
1160 | | PRIV_HSTORE_DEFINE_PRIMTYS(uint16_t, "short"); |
1161 | | PRIV_HSTORE_DEFINE_PRIMTYS(int32_t, "int"); |
1162 | | PRIV_HSTORE_DEFINE_PRIMTYS(uint32_t, "int"); |
1163 | | PRIV_HSTORE_DEFINE_PRIMTYS(int64_t, "long"); |
1164 | | PRIV_HSTORE_DEFINE_PRIMTYS(uint64_t, "long"); |
1165 | | #if defined(__APPLE__) && defined(__MACH__) |
1166 | | PRIV_HSTORE_DEFINE_PRIMTYS(size_t, "long"); |
1167 | | #endif |
1168 | | PRIV_HSTORE_DEFINE_PRIMTYS(__int128, "int128"); |
1169 | | PRIV_HSTORE_DEFINE_PRIMTYS(float, "float"); |
1170 | | PRIV_HSTORE_DEFINE_PRIMTYS(double, "double"); |
1171 | | |
1172 | | // store unit |
1173 | | template <> |
1174 | | struct store<unit> { |
1175 | | static const bool can_memcpy = false; |
1176 | 0 | static ty::desc type() { return ty::prim("unit"); } |
1177 | 0 | static size_t size() { return 0; } |
1178 | 0 | static bool write(wpipe&, const unit&) { return true; } |
1179 | | }; |
1180 | | |
1181 | | // store fixed-length arrays |
1182 | | template <typename T, size_t N> |
1183 | | struct fixedArrTyDesc { static ty::desc type() { return ty::array(store<T>::type(), ty::nat(N)); } }; |
1184 | | |
1185 | | template <typename T, size_t N> |
1186 | | struct store<T[N], typename tbool<store<T>::can_memcpy>::type> : public fixedArrTyDesc<T,N> { |
1187 | | static const bool can_memcpy = true; |
1188 | | static size_t size (const T (&)[N]) { return sizeof(T)*N; } |
1189 | | static bool write(wpipe& p, const T (&v)[N]) { return p.write(reinterpret_cast<const uint8_t*>(v), sizeof(T)*N); } |
1190 | | }; |
1191 | | template <typename T, size_t N> |
1192 | | struct store<std::array<T, N>, typename tbool<store<T>::can_memcpy>::type> : public fixedArrTyDesc<T, N> { |
1193 | | static const bool can_memcpy = true; |
1194 | | static size_t size (const std::array<T, N>&) { return sizeof(T)*N; } |
1195 | | static bool write(wpipe& p, const std::array<T, N>& x) { return p.write(reinterpret_cast<const uint8_t*>(&x), size(x)); } |
1196 | | }; |
1197 | | template <typename T, size_t N> |
1198 | | struct store<T[N], typename tbool<!store<T>::can_memcpy>::type> : public fixedArrTyDesc<T,N> { |
1199 | | static const bool can_memcpy = false; |
1200 | | static size_t size(const T (&v)[N]) { size_t s = 0; for (size_t i = 0; i < N; ++i) { s += store<T>::size(v[i]); } return s; } |
1201 | | static bool write(wpipe& p, const T (&v)[N]) { for (size_t i = 0; i < N; ++i) { if (!store<T>::write(p, v[i])) { return false; } } return true; } |
1202 | | }; |
1203 | | template <typename T, size_t N> |
1204 | | struct store<std::array<T, N>, typename tbool<!store<T>::can_memcpy>::type> : public fixedArrTyDesc<T, N> { |
1205 | | static const bool can_memcpy = false; |
1206 | | static size_t size (const std::array<T, N>& x) { size_t n = 0; for (size_t i = 0; i < N; ++i) { n += store<T>::size(x[i]); } return n; } |
1207 | | static bool write(wpipe& p, const std::array<T, N>& x) { for (size_t i = 0; i < N; ++i) { if (!store<T>::write(p, x[i])) return false; } return true; } |
1208 | | }; |
1209 | | |
1210 | | // store strings |
1211 | | template <> |
1212 | | struct store<const char*> { |
1213 | | static const bool can_memcpy = false; |
1214 | 0 | static ty::desc type () { return ty::array(ty::prim("char")); } |
1215 | 0 | static size_t size (const char* s) { return sizeof(size_t) + strlen(s); } |
1216 | 0 | static bool write(wpipe& p, const char* s) { size_t n = strlen(s); return store<size_t>::write(p, n) && p.write(reinterpret_cast<const uint8_t*>(s), n); } |
1217 | | }; |
1218 | | template <> |
1219 | | struct store<char*> { |
1220 | | static const bool can_memcpy = false; |
1221 | 0 | static ty::desc type () { return ty::array(ty::prim("char")); } |
1222 | 0 | static size_t size (char* s) { return sizeof(size_t) + strlen(s); } |
1223 | 0 | static bool write(wpipe& p, char* s) { size_t n = strlen(s); return store<size_t>::write(p, n) && p.write(reinterpret_cast<const uint8_t*>(s), n); } |
1224 | | }; |
1225 | | template <> |
1226 | | struct store<std::string> { |
1227 | | static const bool can_memcpy = false; |
1228 | 0 | static ty::desc type () { return ty::array(ty::prim("char")); } |
1229 | 0 | static size_t size (const std::string& s) { return sizeof(size_t) + s.size(); } |
1230 | 0 | static bool write(wpipe& p, const std::string& s) { size_t n = s.size(); return store<size_t>::write(p, n) && p.write(reinterpret_cast<const uint8_t*>(s.data()), n); } |
1231 | | }; |
1232 | | |
1233 | | // support storage of vectors |
1234 | | template <typename T> |
1235 | | struct store<std::vector<T>, typename tbool<store<T>::can_memcpy>::type> { |
1236 | | static const bool can_memcpy = false; |
1237 | | static ty::desc type () { return ty::array(store<T>::type()); } |
1238 | | static size_t size (const std::vector<T>& xs) { return sizeof(size_t) + (xs.size() * sizeof(T)); } |
1239 | | static bool write(wpipe& p, const std::vector<T>& xs) { size_t n = xs.size(); return store<size_t>::write(p, n) && p.write(reinterpret_cast<const uint8_t*>(&xs[0]), n * sizeof(T)); } |
1240 | | }; |
1241 | | |
1242 | | template <typename T> |
1243 | | struct store<std::vector<T>, typename tbool<!store<T>::can_memcpy>::type> { |
1244 | | static const bool can_memcpy = false; |
1245 | | static ty::desc type() { return ty::array(store<T>::type()); } |
1246 | | static size_t size(const std::vector<T>& xs) { |
1247 | | size_t t = sizeof(size_t); |
1248 | | for (const auto& x : xs) { |
1249 | | t += store<T>::size(x); |
1250 | | } |
1251 | | return t; |
1252 | | } |
1253 | | static bool write(wpipe& p, const std::vector<T>& xs) { |
1254 | | size_t n = xs.size(); |
1255 | | if (!store<size_t>::write(p, n)) { |
1256 | | return false; |
1257 | | } |
1258 | | for (const auto& x : xs) { |
1259 | | if (!store<T>::write(p, x)) { |
1260 | | return false; |
1261 | | } |
1262 | | } |
1263 | | return true; |
1264 | | } |
1265 | | }; |
1266 | | |
1267 | | template <> |
1268 | | struct store<std::vector<bool>> { |
1269 | | static const bool can_memcpy = false; |
1270 | 0 | static ty::desc type() { return ty::array(store<bool>::type()); } |
1271 | 0 | static size_t size(const std::vector<bool>& xs) { return sizeof(size_t) + (xs.size() * sizeof(bool)); } |
1272 | 0 | static bool write(wpipe& p, const std::vector<bool>& xs) { |
1273 | 0 | size_t n = xs.size(); |
1274 | 0 | if (!store<size_t>::write(p, n)) { return false; } |
1275 | 0 | for (const auto& x : xs) { |
1276 | 0 | if (!store<bool>::write(p, x)) { |
1277 | 0 | return false; |
1278 | 0 | } |
1279 | 0 | } |
1280 | 0 | return true; |
1281 | 0 | } |
1282 | | }; |
1283 | | |
1284 | | template <typename T> |
1285 | | struct store<array<T>*, typename tbool<store<T>::can_memcpy>::type> { |
1286 | | static const bool can_memcpy = false; |
1287 | | static ty::desc type() { return ty::array(store<T>::type()); } |
1288 | | static size_t size(const array<T>* xs) { return sizeof(size_t) + xs->size * sizeof(T); } |
1289 | | static bool write(wpipe& p, const array<T>* xs) { return p.write(reinterpret_cast<const uint8_t*>(xs), size(xs)); } |
1290 | | }; |
1291 | | |
1292 | | template <typename T> |
1293 | | struct store<array<T>*, typename tbool<!store<T>::can_memcpy>::type> { |
1294 | | static const bool can_memcpy = false; |
1295 | | static ty::desc type() { return ty::array(store<T>::type()); } |
1296 | | static size_t size(const array<T>* xs) { |
1297 | | size_t size = sizeof(size_t); |
1298 | | for (const auto & x : xs) { |
1299 | | size += store<T>::size(x); |
1300 | | } |
1301 | | return size; |
1302 | | } |
1303 | | static bool write(wpipe& p, const array<T>* xs) { |
1304 | | if (!store<size_t>::write(p, xs->size)) { |
1305 | | return false; |
1306 | | } |
1307 | | for (const auto & x : xs) { |
1308 | | if (!store<T>::write(p, x)) { |
1309 | | return false; |
1310 | | } |
1311 | | } |
1312 | | return true; |
1313 | | } |
1314 | | }; |
1315 | | |
1316 | | /* |
1317 | | * store tuples (this gets a little complicated) |
1318 | | */ |
1319 | | |
1320 | | // a generic method to write tuples as a last resort |
1321 | | template <size_t i, size_t n, typename ... Ts> |
1322 | | struct storeTupleDef { |
1323 | | using H = typename nth<i, Ts...>::type; |
1324 | | using offs = typename tuple<Ts...>::offs; |
1325 | | using Recurse = storeTupleDef<i + 1, n, Ts...>; |
1326 | | |
1327 | | static void fieldDefs(ty::Struct::Fields* fs) { |
1328 | | fs->push_back(ty::Struct::Field(".f" + hobbes::string::from(i), static_cast<int>(offsetAt<i, offs>::value), store<H>::type())); |
1329 | | Recurse::fieldDefs(fs); |
1330 | | } |
1331 | | static ty::desc type() { ty::Struct::Fields fs; fieldDefs(&fs); return ty::record(fs); } |
1332 | | |
1333 | | static size_t size(const tuple<Ts...>& x) { |
1334 | | return store<H>::size(x.template at<i>()) + Recurse::size(x); |
1335 | | } |
1336 | | |
1337 | | static bool incrWrite(wpipe& p, const tuple<Ts...>& x) { |
1338 | | return store<H>::write(p, x.template at<i>()) && |
1339 | | Recurse::incrWrite(p, x); |
1340 | | } |
1341 | | }; |
1342 | | template <size_t n, typename ... Ts> |
1343 | | struct storeTupleDef<n, n, Ts...> { |
1344 | | static void fieldDefs(ty::Struct::Fields*) { } |
1345 | | static ty::desc type() { return ty::prim("unit"); } |
1346 | | static size_t size(const tuple<Ts...>&) { return 0; } |
1347 | | static bool incrWrite(wpipe&, const tuple<Ts...>&) { return true; } |
1348 | | }; |
1349 | | |
1350 | | // when we store standard-layout tuples, we can only bulk memcpy them if: |
1351 | | // * all fields up to the last one are packed (no internal padding, but trailing padding is ok since we can truncate it with memcpy) |
1352 | | // * all field types can be memcopied |
1353 | | template <typename T, typename P = void> |
1354 | | struct storeStdLayoutTuple { }; |
1355 | | |
1356 | | template <typename ... Ts> |
1357 | | struct all_memcpyableFs { static const bool value = true; }; |
1358 | | template <typename T, typename ... Ts> |
1359 | | struct all_memcpyableFs<T, Ts...> { static const bool value = store<T>::can_memcpy && all_memcpyableFs<Ts...>::value; }; |
1360 | | |
1361 | | // the whole tuple can be memcopied |
1362 | | // e.g.: int*int |
1363 | | template <typename ... Ts> |
1364 | | struct storeStdLayoutTuple<tuple<Ts...>, typename tbool<tuple<Ts...>::packed && all_memcpyableFs<Ts...>::value>::type> { |
1365 | | static const bool can_memcpy = true; |
1366 | | static ty::desc type() { return storeTupleDef<0, sizeof...(Ts), Ts...>::type(); } |
1367 | | static size_t size(const tuple<Ts...>&) { return sizeof(tuple<Ts...>); } |
1368 | | static bool write(wpipe& p, const tuple<Ts...>& t) { return p.write(reinterpret_cast<const uint8_t*>(&t), sizeof(t)); } |
1369 | | }; |
1370 | | |
1371 | | // the tuple prefix can be memcopied but the whole thing can't because it has trailing padding |
1372 | | // e.g.: double*int |
1373 | | template <typename ... Ts> |
1374 | | struct storeStdLayoutTuple<tuple<Ts...>, typename tbool<!tuple<Ts...>::packed && tuple<Ts...>::offs::packed && all_memcpyableFs<Ts...>::value>::type> { |
1375 | | static const bool can_memcpy = false; |
1376 | | static ty::desc type() { return storeTupleDef<0, sizeof...(Ts), Ts...>::type(); } |
1377 | | static size_t size(const tuple<Ts...>&) { return tuple<Ts...>::offs::size; } |
1378 | | static bool write(wpipe& p, const tuple<Ts...>& t) { return p.write(reinterpret_cast<const uint8_t*>(&t), tuple<Ts...>::offs::size); } |
1379 | | }; |
1380 | | |
1381 | | // the tuple can't be memcopied |
1382 | | template <typename ... Ts> |
1383 | | struct storeStdLayoutTuple<tuple<Ts...>, typename tbool<(!tuple<Ts...>::packed && !tuple<Ts...>::offs::packed) || !all_memcpyableFs<Ts...>::value>::type> { |
1384 | | static const bool can_memcpy = false; |
1385 | | using Reflect = storeTupleDef<0, sizeof...(Ts), Ts...>; |
1386 | | static ty::desc type() { return Reflect::type(); } |
1387 | | static size_t size(const tuple<Ts...>& t) { return Reflect::size(t); } |
1388 | | static bool write(wpipe& p, const tuple<Ts...>& t) { return Reflect::incrWrite(p, t); } |
1389 | | }; |
1390 | | |
1391 | | // many types can be stored "as if" some standard layout tuple |
1392 | | // those types can describe themselves however they would like, then defer to this implementation to actually write and compute size |
1393 | | template <typename R, typename T> |
1394 | | struct storeAsIfTuple { |
1395 | | static const bool can_memcpy = storeStdLayoutTuple<T>::can_memcpy; |
1396 | | |
1397 | | static size_t size (const R& r) { return storeStdLayoutTuple<T>::size(*reinterpret_cast<const T*>(&r)); } |
1398 | | static bool write(wpipe& p, const R& r) { return storeStdLayoutTuple<T>::write(p, *reinterpret_cast<const T*>(&r)); } |
1399 | | }; |
1400 | | |
1401 | | // store pairs |
1402 | | template <typename U, typename V> |
1403 | | struct store<std::pair<U,V>> : public storeAsIfTuple<std::pair<U,V>, tuple<U,V>> { |
1404 | | static ty::desc type() { return storeStdLayoutTuple<tuple<U,V>>::type(); } |
1405 | | }; |
1406 | | |
1407 | | // store reflective structs |
1408 | | #define DEFINE_HSTORE_STRUCT(T, FIELDS...) DEFINE_STRUCT(T, FIELDS) |
1409 | | |
1410 | | struct defStructF { |
1411 | | ty::Struct::Fields* fs; |
1412 | 0 | defStructF(ty::Struct::Fields* fs) : fs(fs) { } |
1413 | | template <typename T> |
1414 | | void visit(const char* fname) { |
1415 | | this->fs->push_back(ty::Struct::Field(fname, -1, store<T>::type())); |
1416 | | } |
1417 | | }; |
1418 | | template <typename T> |
1419 | | struct store<T, typename tbool<T::is_hmeta_struct>::type> : public storeAsIfTuple<T, typename T::as_tuple_type> { |
1420 | | static std::string storageName() { return T::_hmeta_struct_type_name(); } |
1421 | | |
1422 | | static ty::desc type() { |
1423 | | ty::Struct::Fields fs; |
1424 | | defStructF df(&fs); |
1425 | | T::meta(df); |
1426 | | return ty::record(fs); |
1427 | | } |
1428 | | }; |
1429 | | |
1430 | | // this macro is deprecated, but has been used to define a struct to record s.t. it stores via memcpy |
1431 | | // for backward compatibility, assert that a packed struct is packed |
1432 | | // this breaks if users don't declare structs in a way that would be packed without the packed annotation |
1433 | | // but allows all structs that happen to be packed to share the same memcpy logic |
1434 | | #define DEFINE_PACKED_HSTORE_STRUCT(T, FIELDS...) \ |
1435 | | DEFINE_STRUCT(T, FIELDS); \ |
1436 | | static_assert(::hobbes::storage::storeStdLayoutTuple<T::as_tuple_type>::can_memcpy, "declaration of packed struct found inconsistent with internal padding (add explicit fields or re-order fields to eliminate padding)") |
1437 | | |
1438 | | // support storing "struct views" (select fields / const accessor functions) out of a type |
1439 | | template <typename T, typename F> |
1440 | | static F T::* resolve(F T::*); |
1441 | | template <typename T, typename R> |
1442 | | static auto resolve(R (T::*K)() const) -> decltype(K); |
1443 | | |
1444 | | template <typename T> struct member; |
1445 | | |
1446 | | template <typename T, typename F> |
1447 | | struct member<F T::*> { |
1448 | | using type = F; |
1449 | | using Acc = F T::*; |
1450 | | static const F& read(const T& x, Acc a) { return x.*a; } |
1451 | | }; |
1452 | | template <typename T, typename F> |
1453 | | struct member<F (T::*)() const> { |
1454 | | using type = F; |
1455 | | using Acc = F (T::*)() const; |
1456 | | static F read(const T& x, Acc a) { return (x.*a)(); } |
1457 | | }; |
1458 | | |
1459 | | #define PRIV_HSTORE_DSV_T(f) member<decltype(resolve(&SelfT::f))>::type |
1460 | | #define PRIV_HSTORE_DSV_READ(f) member<decltype(resolve(&SelfT::f))>::read(x, std::integral_constant<member<decltype(resolve(&SelfT::f))>::Acc, &SelfT::f>::value) |
1461 | | #define PRIV_HSTORE_DSV_DEF_FIELD(f) fs.push_back(ty::Struct::Field(#f, -1, store<PRIV_HSTORE_DSV_T(f)>::type())); |
1462 | | #define PRIV_HSTORE_DSV_SIZE(f) + store< PRIV_HSTORE_DSV_T(f) >::size(PRIV_HSTORE_DSV_READ(f)) |
1463 | | #define PRIV_HSTORE_DSV_WRITE(f) && store< PRIV_HSTORE_DSV_T(f) >::write(p, PRIV_HSTORE_DSV_READ(f)) |
1464 | | |
1465 | | #define DEFINE_HSTORE_STRUCT_VIEW(T, FIELDS...) \ |
1466 | | namespace hobbes { namespace storage { \ |
1467 | | template <> \ |
1468 | | struct store<T> { \ |
1469 | | typedef T SelfT; \ |
1470 | | static std::string storageName() { return #T; } \ |
1471 | | static ty::desc type() { ty::Struct::Fields fs; PRIV_HPPF_MAPL(PRIV_HSTORE_DSV_DEF_FIELD, FIELDS); return ty::record(fs); } \ |
1472 | | static size_t size(const T& x) { return 0 PRIV_HPPF_MAPL(PRIV_HSTORE_DSV_SIZE, FIELDS); } \ |
1473 | | static bool write(wpipe& p, const T& x) { return true PRIV_HPPF_MAPL(PRIV_HSTORE_DSV_WRITE, FIELDS); } \ |
1474 | | }; \ |
1475 | | template <> \ |
1476 | | struct store<T*> { \ |
1477 | | typedef T SelfT; \ |
1478 | | static std::string storageName() { return #T; } \ |
1479 | | static ty::desc type() { ty::Struct::Fields fs; PRIV_HPPF_MAPL(PRIV_HSTORE_DSV_DEF_FIELD, FIELDS); return ty::record(fs); } \ |
1480 | | static size_t size(const T* px) { const T& x = *px; return 0 PRIV_HPPF_MAPL(PRIV_HSTORE_DSV_SIZE, FIELDS); } \ |
1481 | | static bool write(wpipe& p, const T* px) { const T& x = *px; return true PRIV_HPPF_MAPL(PRIV_HSTORE_DSV_WRITE, FIELDS); } \ |
1482 | | }; \ |
1483 | | template <> \ |
1484 | | struct store<const T*> { \ |
1485 | | typedef T SelfT; \ |
1486 | | static std::string storageName() { return #T; } \ |
1487 | | static ty::desc type() { ty::Struct::Fields fs; PRIV_HPPF_MAPL(PRIV_HSTORE_DSV_DEF_FIELD, FIELDS); return ty::record(fs); } \ |
1488 | | static size_t size(const T* px) { const T& x = *px; return 0 PRIV_HPPF_MAPL(PRIV_HSTORE_DSV_SIZE, FIELDS); } \ |
1489 | | static bool write(wpipe& p, const T* px) { const T& x = *px; return true PRIV_HPPF_MAPL(PRIV_HSTORE_DSV_WRITE, FIELDS); } \ |
1490 | | }; \ |
1491 | | }} |
1492 | | #define HSTORE_MAKE_FRIEND template <typename _> friend class hobbes::storage::store; |
1493 | | |
1494 | | // store std::tuples |
1495 | | // (std::tuples don't have standard layout so we need to store them the hard way) |
1496 | | template <size_t i, size_t e, typename ... Ts> |
1497 | | struct storeTuple { |
1498 | | static size_t size(const std::tuple<Ts...>& t) { |
1499 | | return store<typename std::tuple_element<i, std::tuple<Ts...>>::type>::size(std::get<i>(t)) + |
1500 | | storeTuple<i+1, e, Ts...>::size(t); |
1501 | | } |
1502 | | static bool write(wpipe& p, const std::tuple<Ts...>& t) { |
1503 | | return store<typename std::tuple_element<i, std::tuple<Ts...>>::type>::write(p, std::get<i>(t)) && |
1504 | | storeTuple<i+1, e, Ts...>::write(p, t); |
1505 | | } |
1506 | | }; |
1507 | | template <size_t e, typename ... Ts> |
1508 | | struct storeTuple<e, e, Ts...> { |
1509 | | static size_t size(const std::tuple<Ts...>&) { return 0; } |
1510 | | static bool write(wpipe&, const std::tuple<Ts...>&) { return true; } |
1511 | | }; |
1512 | | template <typename ... Ts> |
1513 | | struct store< std::tuple<Ts...> > { |
1514 | | static ty::desc type() { return storeStdLayoutTuple<tuple<Ts...>>::type(); } |
1515 | | |
1516 | | static size_t size(const std::tuple<Ts...>& t) { |
1517 | | return storeTuple<0, sizeof...(Ts), Ts...>::size(t); |
1518 | | } |
1519 | | static bool write(wpipe& p, const std::tuple<Ts...>& t) { |
1520 | | return storeTuple<0, sizeof...(Ts), Ts...>::write(p, t); |
1521 | | } |
1522 | | }; |
1523 | | |
1524 | | // store enumerations |
1525 | | template <typename T> |
1526 | | struct store<T, typename tbool<T::is_hmeta_enum>::type> { |
1527 | | static const bool can_memcpy = true; |
1528 | | |
1529 | | static ty::desc type() { return ty::enumdef(store<typename T::rep_t>::type(), T::meta()); } |
1530 | | static size_t size(const T&) { return sizeof(typename T::rep_t); } |
1531 | | static bool write(wpipe& p, const T& x) { return p.write(reinterpret_cast<const uint8_t*>(&x), sizeof(x)); } |
1532 | | }; |
1533 | | |
1534 | | // store variants |
1535 | | template <size_t i, size_t n, typename ... Ts> |
1536 | | struct storeVariantDef { |
1537 | | using H = typename nth<i, Ts...>::type; |
1538 | | using Recurse = storeVariantDef<i + 1, n, Ts...>; |
1539 | | |
1540 | | static void ctorDefs(ty::Variant::Ctors* cs) { |
1541 | | cs->push_back(ty::Variant::Ctor(".f" + hobbes::string::from(i), static_cast<int>(i), store<H>::type())); |
1542 | | Recurse::ctorDefs(cs); |
1543 | | } |
1544 | | static ty::desc type() { ty::Variant::Ctors cs; ctorDefs(&cs); return ty::variant(cs); } |
1545 | | }; |
1546 | | template <size_t n, typename ... Ts> |
1547 | | struct storeVariantDef<n, n, Ts...> { |
1548 | | static void ctorDefs(ty::Variant::Ctors*) { } |
1549 | | static ty::desc type() { return ty::prim("void"); } |
1550 | | }; |
1551 | | |
1552 | | template <size_t tag, typename T, typename M> |
1553 | | struct variantGenSize { |
1554 | | static size_t fn(T* vd, int) { |
1555 | | return store<T>::size(*vd); |
1556 | | } |
1557 | | }; |
1558 | | template <size_t tag, typename T, typename M> |
1559 | | struct variantGenWrite { |
1560 | | static bool fn(T* vd, wpipe* f) { |
1561 | | return store<uint32_t>::write(*f, static_cast<uint32_t>(tag)) && store<T>::write(*f, *vd); |
1562 | | } |
1563 | | }; |
1564 | | template <typename ... Ts> |
1565 | | struct store<variant<Ts...>> : public storeVariantDef<0, sizeof...(Ts), Ts...> { |
1566 | | static const bool can_memcpy = false; |
1567 | | |
1568 | | static size_t size(const variant<Ts...>& x) { |
1569 | | return sizeof(uint32_t) + x.template apply<size_t, variantGenSize, void, int>(0); |
1570 | | } |
1571 | | |
1572 | | static bool write(wpipe& p, const variant<Ts...>& x) { |
1573 | | return x.template apply<bool, variantGenWrite, void, wpipe*>(&p); |
1574 | | } |
1575 | | }; |
1576 | | |
1577 | | // store variants with named constructors |
1578 | | struct descVariantF { |
1579 | | ty::Variant::Ctors* ctors; |
1580 | 0 | descVariantF(ty::Variant::Ctors* ctors) : ctors(ctors) { } |
1581 | | |
1582 | | template <typename T> |
1583 | | void ctor(const char* n, int id) { |
1584 | | this->ctors->push_back(ty::Variant::Ctor(n, id, store<T>::type())); |
1585 | | } |
1586 | | }; |
1587 | | |
1588 | | template <typename T> |
1589 | | struct store<T, typename tbool<T::is_hmeta_variant>::type> { |
1590 | | using VT = typename T::as_variant_type; |
1591 | | static const bool can_memcpy = store<VT>::can_memcpy; |
1592 | | |
1593 | | static ty::desc type() { |
1594 | | ty::Variant::Ctors cs; |
1595 | | descVariantF f(&cs); |
1596 | | T::meta(f); |
1597 | | return ty::variant(cs); |
1598 | | } |
1599 | | |
1600 | | static size_t size (const T& x) { return store<VT>::size(*reinterpret_cast<const VT*>(&x)); } |
1601 | | static bool write(wpipe& p, const T& x) { return store<VT>::write(p, *reinterpret_cast<const VT*>(&x)); } |
1602 | | }; |
1603 | | |
1604 | | // store recursive types |
1605 | | struct recursion { |
1606 | | void* value; |
1607 | 0 | recursion(void* x) : value(x) { } |
1608 | | }; |
1609 | | |
1610 | | template <typename T> |
1611 | | struct recursive { |
1612 | | using value_type = T; |
1613 | | value_type value; |
1614 | | recursive(const value_type& x) : value(x) { } |
1615 | | }; |
1616 | | |
1617 | | template <> |
1618 | | struct store<recursion> { |
1619 | 0 | static ty::desc type() { |
1620 | 0 | return ty::var("x"); |
1621 | 0 | } |
1622 | | |
1623 | | using RecSizeF = size_t (*)(const recursion &); |
1624 | 0 | static RecSizeF& sizeF() { |
1625 | 0 | thread_local RecSizeF fn = nullptr; |
1626 | 0 | return fn; |
1627 | 0 | } |
1628 | 0 | static size_t size(const recursion& x) { |
1629 | 0 | return sizeF()(x); |
1630 | 0 | } |
1631 | | |
1632 | | using RecWriteF = bool (*)(wpipe &, const recursion &); |
1633 | 0 | static RecWriteF& writeF() { |
1634 | 0 | thread_local RecWriteF fn = nullptr; |
1635 | 0 | return fn; |
1636 | 0 | } |
1637 | 0 | static bool write(wpipe& p, const recursion& x) { |
1638 | 0 | return writeF()(p, x); |
1639 | 0 | } |
1640 | | }; |
1641 | | |
1642 | | template <typename T> |
1643 | | struct store<recursive<T>> { |
1644 | | static void encode(bytes*) { |
1645 | | return ty::recursive("x", store<T>::type()); |
1646 | | } |
1647 | | static size_t recSize(const recursion& x) { |
1648 | | return size(*reinterpret_cast<const recursive<T>*>(x.value)); |
1649 | | } |
1650 | | static size_t size(const recursive<T>& x) { |
1651 | | using RSF = typename store<recursion>::RecSizeF; |
1652 | | RSF sf = store<recursion>::sizeF(); |
1653 | | store<recursion>::sizeF() = &store<recursive<T>>::recSize; |
1654 | | size_t r = store<T>::size(x.value); |
1655 | | store<recursion>::sizeF() = sf; |
1656 | | return r; |
1657 | | } |
1658 | | |
1659 | | static bool recWrite(wpipe& p, const recursion& x) { |
1660 | | return write(p, *reinterpret_cast<const recursive<T>*>(x.value)); |
1661 | | } |
1662 | | static bool write(wpipe& p, const recursive<T>& x) { |
1663 | | using RWF = typename store<recursion>::RecWriteF; |
1664 | | RWF sf = store<recursion>::writeF(); |
1665 | | store<recursion>::writeF() = &store<recursive<T>>::recWrite; |
1666 | | bool r = store<T>::write(p, x.value); |
1667 | | store<recursion>::writeF() = sf; |
1668 | | return r; |
1669 | | } |
1670 | | }; |
1671 | | |
1672 | | // store opaque type aliases |
1673 | | template <typename T> |
1674 | | struct store<T, typename tbool<T::is_hmeta_alias>::type> { |
1675 | | using RT = typename T::type; |
1676 | | static const bool can_memcpy = store<RT>::can_memcpy; |
1677 | | |
1678 | | static ty::desc type () { return ty::prim(T::name(), store<RT>::type()); ; } |
1679 | | static size_t size (const T& x) { return store<RT>::size(x.value); } |
1680 | | static bool write(wpipe& p, const T& x) { return store<RT>::write(p, x.value); } |
1681 | | }; |
1682 | | |
1683 | | // support values carrying field names (easier to record structs without making an explicit struct type) |
1684 | | template <typename F, typename T> |
1685 | | struct namedValue { |
1686 | | T value; |
1687 | | namedValue(const T& v) : value(v) { } |
1688 | | }; |
1689 | | |
1690 | | template <typename F, typename T> |
1691 | | struct store<namedValue<F,T>> { |
1692 | | static ty::desc type () { return store<T>::type(); } |
1693 | | static size_t size (const namedValue<F,T>& x) { return store<T>::size(x.value); } |
1694 | | static bool write(wpipe& p, const namedValue<F,T>& x) { return store<T>::write(p, x.value); } |
1695 | | }; |
1696 | | |
1697 | | #define HNAME(N,E) ::hobbes::storage::namedValue<PRIV_HPPF_TSTR(#N),decltype(E)>(E) |
1698 | | |
1699 | | // allow inference of log statement names from payload types |
1700 | | using tynamehints = void (*)(std::vector<std::string> *); |
1701 | | template <typename T> |
1702 | | struct nameAt { |
1703 | | template <typename U, std::string (*P)()> struct HasStorageName { }; |
1704 | | template <typename U> static std::string is(const HasStorageName<U, &U::storageName>*) { return U::storageName(); } |
1705 | | template <typename U> static std::string is(...) { return ""; } |
1706 | | }; |
1707 | | |
1708 | | template <typename T> |
1709 | | struct storeNames { |
1710 | | static void accum(std::vector<std::string>* ns) { |
1711 | | std::string n = nameAt<T>::template is<store<T>>(0); |
1712 | | if (!n.empty()) { |
1713 | | ns->push_back(n); |
1714 | | } |
1715 | | } |
1716 | | }; |
1717 | | |
1718 | | // make a function for describing store payload types |
1719 | | template <typename T> |
1720 | | struct hstore_argl_name { |
1721 | | static const bool isTuple = true; |
1722 | | static void nameDesc(const char* prefix, size_t idx, char* buffer, size_t buflen) { |
1723 | | snprintf(buffer, buflen, prefix, static_cast<unsigned long long>(idx)); |
1724 | | } |
1725 | | }; |
1726 | | template <typename F, typename T> |
1727 | | struct hstore_argl_name<namedValue<F,T>> { |
1728 | | static const bool isTuple = false; |
1729 | | static void nameDesc(const char*, size_t, char* buffer, size_t buflen) { |
1730 | | strncpy(buffer, F::str(), buflen); |
1731 | | } |
1732 | | }; |
1733 | | |
1734 | | template <typename ... Ts> |
1735 | | struct hstore_payload_types { |
1736 | | using head_type = unit; |
1737 | | static const size_t count = 0; |
1738 | | static const bool isTuple = true; |
1739 | | static void fields(const char*,size_t,ty::Struct::Fields*) {} |
1740 | | static void nameHints(std::vector<std::string>*) {} |
1741 | | }; |
1742 | | template <typename T, typename ... Ts> |
1743 | | struct hstore_payload_types<T,Ts...> { |
1744 | | using head_type = T; |
1745 | | static const size_t count = 1 + hstore_payload_types<Ts...>::count; |
1746 | | static const bool isTuple = hstore_argl_name<T>::isTuple && hstore_payload_types<Ts...>::isTuple; |
1747 | | static void fields(const char* fmt, size_t f, ty::Struct::Fields* fs) { |
1748 | | char fn[128]; |
1749 | | hstore_argl_name<T>::nameDesc(fmt, f, fn, sizeof(fn)); |
1750 | | fs->push_back(ty::Struct::Field(fn, -1, store<T>::type())); |
1751 | | |
1752 | | hstore_payload_types<Ts...>::fields(fmt, f+1, fs); |
1753 | | } |
1754 | | static void nameHints(std::vector<std::string>* ns) { |
1755 | | storeNames<T>::accum(ns); |
1756 | | hstore_payload_types<Ts...>::nameHints(ns); |
1757 | | } |
1758 | | }; |
1759 | | template <typename ... Ts> |
1760 | | hstore_payload_types<Ts...> makePayloadTypes(const Ts&...) { |
1761 | | return hstore_payload_types<Ts...>(); |
1762 | | } |
1763 | | |
1764 | | using tydescfn = void (*)(bytes *, std::string *); |
1765 | | template <typename TyList> |
1766 | | void makeTyDescF(bytes* e, std::string* d) { |
1767 | | ty::desc td; |
1768 | | |
1769 | | if (!TyList::isTuple || TyList::count > 1) { |
1770 | | ty::Struct::Fields fs; |
1771 | | TyList::fields(TyList::isTuple ? ".f%lld" : "field%lld", 0, &fs); |
1772 | | td = ty::record(fs); |
1773 | | } else if (TyList::count == 0) { |
1774 | | td = ty::prim("unit"); |
1775 | | } else { |
1776 | | td = store<typename TyList::head_type>::type(); |
1777 | | } |
1778 | | |
1779 | | if (e) { |
1780 | | ty::encode(td, e); |
1781 | | } |
1782 | | if (d) { |
1783 | | *d = ty::show(td); |
1784 | | } |
1785 | | } |
1786 | | |
1787 | | template <uint32_t* X> |
1788 | | uint32_t forceRegistration() { return *X; } |
1789 | | template <typename Group, Group* G, typename File, uint32_t Line, typename StmtName, size_t Flags, typename FormatStr, typename ArgTyList> |
1790 | | struct StorageStatement { |
1791 | | static uint32_t id; |
1792 | | StorageStatement() { forceRegistration<&id>(); } |
1793 | | }; |
1794 | | template <typename Group, Group* G, typename File, uint32_t Line, typename StmtName, size_t Flags, typename FormatStr, typename ArgTyList> |
1795 | | uint32_t StorageStatement<Group, G, File, Line, StmtName, Flags, FormatStr, ArgTyList>::id = G->allocateStorageStatement(File::str(), Line, StmtName::str(), Flags, FormatStr::str(), &makeTyDescF<ArgTyList>, &ArgTyList::nameHints); |
1796 | | |
1797 | | // allow static registration of storage points |
1798 | | enum CommitMethod { |
1799 | | AutoCommit = 0, |
1800 | | ManualCommit |
1801 | | }; |
1802 | | |
1803 | | template <typename Name, CommitMethod cm> |
1804 | | struct StorageGroup { |
1805 | | struct StmtData { |
1806 | | tydescfn tdesc; |
1807 | | uint64_t flags; |
1808 | | std::string fmtstr; |
1809 | | std::string file; |
1810 | | uint32_t line; |
1811 | | uint32_t id; |
1812 | | }; |
1813 | | using StorageStatements = std::map<std::string, StmtData>; |
1814 | | |
1815 | | StorageStatements* statements; |
1816 | | PipeQOS qos; |
1817 | | size_t mempages; |
1818 | | WaitPolicy wp; |
1819 | | bool enabled; |
1820 | | |
1821 | | std::mutex mqmtx; |
1822 | | int mqserver; |
1823 | | static thread_local wpipe* pipe; |
1824 | | static std::vector<ProcThread> pts; |
1825 | | |
1826 | | constexpr StorageGroup(size_t pagec, const PipeQOS qos) : StorageGroup(pagec, qos, Platform) {} |
1827 | | constexpr StorageGroup(size_t pagec, const PipeQOS qos, const WaitPolicy wp) |
1828 | | : statements(nullptr), qos(qos), mempages(pagec), wp(wp), enabled(true), mqserver(-1) {} |
1829 | | |
1830 | | ~StorageGroup() { |
1831 | | delete this->statements; |
1832 | | delete this->pipe; |
1833 | | } |
1834 | | |
1835 | | void prepareMeta(bytes* meta) { |
1836 | | if (!this->statements) return; // if nothing to record, nothing to prepare |
1837 | | |
1838 | | ty::w(HSTORE_VERSION, meta); |
1839 | | ty::w(static_cast<int>(this->qos), meta); |
1840 | | ty::w(static_cast<int>(cm), meta); |
1841 | | |
1842 | | // write the count of storage statements, then each statement's static data |
1843 | | ty::w(static_cast<uint32_t>(this->statements->size()), meta); |
1844 | | for (auto s : *this->statements) { |
1845 | | ty::ws(s.first, meta); |
1846 | | ty::w (s.second.flags, meta); |
1847 | | ty::ws(s.second.fmtstr, meta); |
1848 | | ty::ws(s.second.file, meta); |
1849 | | ty::w (s.second.line, meta); |
1850 | | ty::w (s.second.id, meta); |
1851 | | |
1852 | | bytes td; |
1853 | | s.second.tdesc(&td,0); |
1854 | | ty::ws(td, meta); |
1855 | | } |
1856 | | } |
1857 | | |
1858 | | wpipe& out() { |
1859 | | if (PRIV_HSTORE_LIKELY(this->pipe != 0)) { |
1860 | | return *this->pipe; |
1861 | | } else { |
1862 | | std::lock_guard<std::mutex> mqguard(this->mqmtx); |
1863 | | pts.push_back(thisProcThread()); |
1864 | | |
1865 | | // allocate a shared memory queue for this group, register it with the group server |
1866 | | bytes meta; |
1867 | | prepareMeta(&meta); |
1868 | | |
1869 | | size_t pagesz = sysconf(_SC_PAGESIZE); |
1870 | | size_t pagec = 1 + (meta.size() / pagesz) + std::max<size_t>(this->mempages, 10); |
1871 | | |
1872 | | this->pipe = |
1873 | | new wpipe( |
1874 | | new writer(meta, sharedMemName(Name::str()), pagesz, pagec, wp), |
1875 | | this->qos, |
1876 | | /*if blocked 10ms*/ 10000000L, |
1877 | | /*reconnect if needed*/ |
1878 | | [&](){ |
1879 | | std::lock_guard<std::mutex> mqguard(this->mqmtx); |
1880 | | reconnectSHM(&this->mqserver, Name::str(), wp, pts); |
1881 | | } |
1882 | | ); |
1883 | | |
1884 | | try { |
1885 | | registerSHMAlloc(&this->mqserver, Name::str(), wp); |
1886 | | } catch (...) { |
1887 | | // unreliable mode can accept connection failures |
1888 | | if (this->qos != Unreliable) { |
1889 | | throw; |
1890 | | } |
1891 | | } |
1892 | | return *this->pipe; |
1893 | | } |
1894 | | } |
1895 | | |
1896 | | inline void init() { |
1897 | | out(); |
1898 | | } |
1899 | | |
1900 | | inline void commit() { |
1901 | | out().commit(); |
1902 | | } |
1903 | | |
1904 | | inline void rollback() { |
1905 | | out().rollback(); |
1906 | | } |
1907 | | |
1908 | | inline void enable() { |
1909 | | this->enabled = true; |
1910 | | } |
1911 | | |
1912 | | inline void disable() { |
1913 | | this->enabled = false; |
1914 | | } |
1915 | | |
1916 | | inline void reconnect() { |
1917 | | std::lock_guard<std::mutex> mqguard(this->mqmtx); |
1918 | | reconnectSHM(&this->mqserver, Name::str(), this->wp, pts); |
1919 | | } |
1920 | | |
1921 | | // use the log statement name provided by the user (if applicable) |
1922 | | // if the name is auto:N, then use either N or a name inferred from the type being logged |
1923 | | // for auto-assigned names, append an incrementing counter if necessary to make the name unique |
1924 | | static bool isAutoName(const std::string& x) { |
1925 | | return x.substr(0, 5) == "auto:"; |
1926 | | } |
1927 | | |
1928 | | static std::string decideName(const std::string& x, tynamehints tnhints) { |
1929 | | if (!isAutoName(x)) { |
1930 | | return x; |
1931 | | } else { |
1932 | | std::string n; |
1933 | | std::vector<std::string> ns; |
1934 | | tnhints(&ns); |
1935 | | if (ns.empty()) { |
1936 | | n = x.substr(5); |
1937 | | } else { |
1938 | | std::ostringstream nss; |
1939 | | nss << ns[0]; |
1940 | | for (size_t i = 1; i < ns.size(); ++i) { |
1941 | | nss << "_" << ns[i]; |
1942 | | } |
1943 | | n = nss.str(); |
1944 | | } |
1945 | | return n; |
1946 | | } |
1947 | | } |
1948 | | |
1949 | | static std::string incrAutoName(const std::string& n) { |
1950 | | std::ostringstream fnss; |
1951 | | static std::map<std::string, std::atomic<size_t>*> ctrs; |
1952 | | auto i = ctrs.find(n); |
1953 | | if (i != ctrs.end()) { |
1954 | | fnss << n << *i->second; |
1955 | | ++*i->second; |
1956 | | } else { |
1957 | | fnss << n << "1"; |
1958 | | ctrs[n] = new std::atomic<size_t>(2); |
1959 | | } |
1960 | | return fnss.str(); |
1961 | | } |
1962 | | |
1963 | | uint32_t addStatement(const std::string& file, uint32_t line, const std::string& name, size_t flags, const std::string& fmtstr, tydescfn tdesc) { |
1964 | | if (this->statements->find(name) != this->statements->end()) { |
1965 | | std::cerr |
1966 | | << "fatal error: duplicate log name in use (" << name << ")" |
1967 | | << std::endl; |
1968 | | |
1969 | | exit(-1); |
1970 | | } |
1971 | | |
1972 | | StmtData d; |
1973 | | d.tdesc = tdesc; |
1974 | | d.flags = flags; |
1975 | | d.fmtstr = fmtstr; |
1976 | | d.file = file; |
1977 | | d.line = line; |
1978 | | d.id = static_cast<uint32_t>(this->statements->size()); |
1979 | | |
1980 | | (*this->statements)[name] = d; |
1981 | | return d.id; |
1982 | | } |
1983 | | |
1984 | | uint32_t allocateStorageStatement(const std::string& file, uint32_t line, const std::string& namet, size_t flags, const std::string& fmtstr, tydescfn tdesc, tynamehints tnhints) { |
1985 | | if (!this->statements) { |
1986 | | this->statements = new StorageStatements(); |
1987 | | } |
1988 | | |
1989 | | auto name = decideName(namet, tnhints); |
1990 | | auto s = this->statements->find(name); |
1991 | | if (s != this->statements->end()) { |
1992 | | // this statement has already been added |
1993 | | // make sure that all identifying parameters are consistent |
1994 | | bytes ety, xty; |
1995 | | s->second.tdesc(&ety,0); |
1996 | | tdesc(&xty,nullptr); |
1997 | | if (ety != xty) { |
1998 | | if (isAutoName(namet)) { |
1999 | | return addStatement(file, line, incrAutoName(name), flags, fmtstr, tdesc); |
2000 | | } else { |
2001 | | std::string etd, xtd; |
2002 | | s->second.tdesc(0,&etd); |
2003 | | tdesc(nullptr,&xtd); |
2004 | | |
2005 | | std::cerr |
2006 | | << "fatal error: incompatible types for store statement '" << name << "' between:\n" |
2007 | | << " " << s->second.file << ":" << s->second.line << " (" << xtd << ")\n" |
2008 | | << "and\n" |
2009 | | << " " << file << ":" << line << " (" << etd << ")\n" |
2010 | | << std::endl; |
2011 | | |
2012 | | exit(-1); |
2013 | | } |
2014 | | } |
2015 | | |
2016 | | if (s->second.fmtstr != fmtstr) { |
2017 | | if (isAutoName(namet)) { |
2018 | | return addStatement(file, line, incrAutoName(name), flags, fmtstr, tdesc); |
2019 | | } else { |
2020 | | std::cerr |
2021 | | << "fatal error: incompatible format strings for store statement '" << name << "' between:\n" |
2022 | | << " " << s->second.file << ":" << s->second.line << " (" << s->second.fmtstr << ")\n" |
2023 | | << "and\n" |
2024 | | << " " << file << ":" << line << " (" << fmtstr << ")\n" |
2025 | | << std::endl; |
2026 | | |
2027 | | exit(-1); |
2028 | | } |
2029 | | } |
2030 | | |
2031 | | if (s->second.flags != flags) { |
2032 | | if (isAutoName(namet)) { |
2033 | | return addStatement(file, line, incrAutoName(name), flags, fmtstr, tdesc); |
2034 | | } else { |
2035 | | std::cerr |
2036 | | << "fatal error: incompatible storage flags for store statement '" << name << "' between:\n" |
2037 | | << " " << s->second.file << ":" << s->second.line << " (" << s->second.flags << ")\n" |
2038 | | << "and\n" |
2039 | | << " " << file << ":" << line << " (" << flags << ")\n" |
2040 | | << std::endl; |
2041 | | |
2042 | | exit(-1); |
2043 | | } |
2044 | | } |
2045 | | |
2046 | | return s->second.id; |
2047 | | } |
2048 | | |
2049 | | // this must be a new statement |
2050 | | return addStatement(file, line, name, flags, fmtstr, tdesc); |
2051 | | } |
2052 | | }; |
2053 | | template <typename Name, CommitMethod cm> |
2054 | | thread_local wpipe* StorageGroup<Name, cm>::pipe = nullptr; |
2055 | | template <typename Name, CommitMethod cm> |
2056 | | std::vector<ProcThread> StorageGroup<Name, cm>::pts; |
2057 | | |
2058 | | // write a storage statement with payload into a pipe |
2059 | | template <typename ... Ts> |
2060 | | struct serialize_values { |
2061 | | static size_t size(const Ts&...) { return 0; } |
2062 | | static bool write(wpipe&, const Ts&...) { return true; } |
2063 | | }; |
2064 | | template <typename T, typename ... Ts> |
2065 | | struct serialize_values<T, Ts...> { |
2066 | | static size_t size(const T& x, const Ts&... xs) { |
2067 | | return store<T>::size(x) + serialize_values<Ts...>::size(xs...); |
2068 | | } |
2069 | | static bool write(wpipe& p, const T& x, const Ts&... xs) { |
2070 | | return store<T>::write(p, x) && serialize_values<Ts...>::write(p, xs...); |
2071 | | } |
2072 | | }; |
2073 | | |
2074 | | template <typename GName, typename ... Ts> |
2075 | | inline bool write(StorageGroup<GName, AutoCommit>* g, uint32_t id, const Ts&... xs) { |
2076 | | wpipe& p = g->out(); |
2077 | | if (!g->enabled) { |
2078 | | return false; |
2079 | | } |
2080 | | if (!p.hasSpaceFor(sizeof(uint32_t) + serialize_values<Ts...>::size(xs...))) { |
2081 | | g->commit(); |
2082 | | } |
2083 | | return store<uint32_t>::write(p, id) && |
2084 | | serialize_values<Ts...>::write(p, xs...); |
2085 | | } |
2086 | | template <typename GName, typename ... Ts> |
2087 | | inline bool write(StorageGroup<GName, ManualCommit>* g, uint32_t id, const Ts&... xs) { |
2088 | | wpipe& p = g->out(); |
2089 | | return g->enabled && |
2090 | | store<uint32_t>::write(p, id) && |
2091 | | serialize_values<Ts...>::write(p, xs...); |
2092 | | } |
2093 | | |
2094 | | // validate arity in log format strings (prevent format strings referring to payload variables that don't exist) |
2095 | | template <size_t N> |
2096 | | static constexpr size_t readInt(const char (&fmt)[N], size_t i, size_t e, size_t n) { |
2097 | | return (i == e) ? n : readInt(fmt, i+1, e, (n*10)+(fmt[i]-'0')); |
2098 | | } |
2099 | | |
2100 | 0 | static constexpr size_t maxV(size_t x, size_t y) { |
2101 | 0 | return (x < y) ? y : x; |
2102 | 0 | } Unexecuted instantiation: fuzz_hog_session.C:hobbes::storage::maxV(unsigned long, unsigned long) Unexecuted instantiation: hstore_bridge.C:hobbes::storage::maxV(unsigned long, unsigned long) |
2103 | | |
2104 | | template <size_t N> |
2105 | | static constexpr size_t maxVarRefS(const char (&fmt)[N], size_t i, size_t s, size_t vri, size_t maxvr) { |
2106 | | return (i >= N) ? maxvr |
2107 | | :(s == 0) ? ((fmt[i] == '\\') ? maxVarRefS(fmt, i+2, 0, 0, maxvr) |
2108 | | :(fmt[i] == '$') ? maxVarRefS(fmt, i+1, 1, i+1, maxvr) |
2109 | | : maxVarRefS(fmt, i+1, 0, 0, maxvr)) |
2110 | | : ((fmt[i] >= '0' && fmt[i] <= '9') ? |
2111 | | maxVarRefS(fmt, i+1, 1, vri, maxvr) |
2112 | | : maxVarRefS(fmt, i, 0, 0, maxV(maxvr, 1+readInt(fmt, vri, i, 0)))); |
2113 | | } |
2114 | | |
2115 | | template <size_t N> |
2116 | | static constexpr size_t maxVarRef(const char (&fmt)[N]) { |
2117 | | return maxVarRefS(fmt, 0, 0, 0, 0); |
2118 | | } |
2119 | | |
2120 | | // create statement groups |
2121 | | #define DECLARE_STORAGE_GROUP(NAME, cm) extern ::hobbes::storage::StorageGroup<PRIV_HPPF_TSTR(#NAME),cm> NAME |
2122 | | #define DEFINE_STORAGE_GROUP(NAME, pagec, qos, cm, ARGS...) ::hobbes::storage::StorageGroup<PRIV_HPPF_TSTR(#NAME),cm> NAME(pagec, qos, ## ARGS) |
2123 | | |
2124 | | // record some data |
2125 | | #define APPLY_HSTORE_STMT(GROUP, NAME, FLAGS, FMTSTR, ARGS...) \ |
2126 | | ::hobbes::storage::write(&GROUP, ({static_assert(::hobbes::storage::maxVarRef(FMTSTR) <= decltype(::hobbes::storage::makePayloadTypes(ARGS))::count, "Log format string and payload arity mismatch"); ::hobbes::storage::StorageStatement<decltype(GROUP),&GROUP,PRIV_HPPF_TSTR(__FILE__),__LINE__,PRIV_HPPF_TSTR(#NAME),FLAGS,PRIV_HPPF_TSTR(FMTSTR),decltype(::hobbes::storage::makePayloadTypes(ARGS))>::id;}), ## ARGS) |
2127 | | |
2128 | | #define HSTORE(GROUP, NAME, ARGS...) APPLY_HSTORE_STMT(GROUP, NAME, 0, "", ## ARGS) |
2129 | | #define HLOG(GROUP, NAME, FMTSTR, ARGS...) APPLY_HSTORE_STMT(GROUP, NAME, 1, FMTSTR, ## ARGS) |
2130 | | |
2131 | | // run a process to read transaction data |
2132 | | struct statement { |
2133 | | std::string name; |
2134 | | uint64_t flags; |
2135 | | std::string fmtstr; |
2136 | | std::string file; |
2137 | | uint32_t line; |
2138 | | uint32_t id; |
2139 | | bytes type; |
2140 | | |
2141 | 0 | inline bool isLog() const { return (this->flags & 1) == 1; } |
2142 | | |
2143 | 0 | inline bool operator==(const statement& rhs) const { |
2144 | 0 | return this->name == rhs.name && |
2145 | 0 | this->flags == rhs.flags && |
2146 | 0 | this->fmtstr == rhs.fmtstr && |
2147 | 0 | this->file == rhs.file && |
2148 | 0 | this->line == rhs.line && |
2149 | 0 | this->id == rhs.id && |
2150 | 0 | this->type == rhs.type; |
2151 | 0 | } |
2152 | | }; |
2153 | | using statements = std::vector<statement>; |
2154 | | |
2155 | 0 | inline size_t rs(const reader::MetaData& md, size_t o, size_t n, uint8_t* b) { |
2156 | 0 | if (o + n <= md.second) { |
2157 | 0 | memcpy(b, md.first + o, n); |
2158 | 0 | return o + n; |
2159 | 0 | } else { |
2160 | 0 | return md.second; |
2161 | 0 | } |
2162 | 0 | } |
2163 | | |
2164 | | template <typename T> |
2165 | 0 | size_t r(const reader::MetaData& md, size_t o, T* t) { |
2166 | 0 | return rs(md, o, sizeof(T), reinterpret_cast<uint8_t*>(t)); |
2167 | 0 | } Unexecuted instantiation: unsigned long hobbes::storage::r<unsigned long>(std::pair<unsigned char const*, unsigned long> const&, unsigned long, unsigned long*) Unexecuted instantiation: unsigned long hobbes::storage::r<unsigned int>(std::pair<unsigned char const*, unsigned long> const&, unsigned long, unsigned int*) Unexecuted instantiation: unsigned long hobbes::storage::r<int>(std::pair<unsigned char const*, unsigned long> const&, unsigned long, int*) |
2168 | | |
2169 | | // the length of a string or byte block in the meta-data section comes out of |
2170 | | // the segment, and it sized the target before anything was read: a length |
2171 | | // near 2^64 was asked of resize() (a bad_alloc, or a very large allocation |
2172 | | // where memory is overcommitted) for a section that could never hold it. The |
2173 | | // copy itself was already bounded -- rs copies nothing when the range does |
2174 | | // not fit -- so a length the section cannot back now leaves an empty result |
2175 | | // the same way, rather than being allocated for first. |
2176 | 0 | inline bool metaHas(const reader::MetaData& md, size_t o, size_t n) { |
2177 | 0 | return o <= md.second && n <= md.second - o; |
2178 | 0 | } |
2179 | | |
2180 | 0 | inline size_t rs(const reader::MetaData& md, size_t o, std::string* s) { |
2181 | 0 | size_t n = 0; |
2182 | 0 | o = r(md, o, &n); |
2183 | 0 | if (!metaHas(md, o, n)) { |
2184 | 0 | return md.second; |
2185 | 0 | } |
2186 | 0 | s->resize(n); |
2187 | 0 | return rs(md, o, n, reinterpret_cast<uint8_t*>(&(*s)[0])); |
2188 | 0 | } |
2189 | | |
2190 | 0 | inline size_t rs(const reader::MetaData& md, size_t o, bytes* s) { |
2191 | 0 | size_t n = 0; |
2192 | 0 | o = r(md, o, &n); |
2193 | 0 | if (!metaHas(md, o, n)) { |
2194 | 0 | return md.second; |
2195 | 0 | } |
2196 | 0 | s->resize(n); |
2197 | 0 | return rs(md, o, n, &(*s)[0]); |
2198 | 0 | } |
2199 | | |
2200 | | class Transaction { |
2201 | | public: |
2202 | | // a persistent transaction to retain log data between consumer sessions (if necessary) |
2203 | 0 | Transaction(std::string fname) : fd(-1), file_size(0), map_size(0), data(nullptr), data_size(nullptr), wi(0), ri(0) { |
2204 | 0 | for (char &i : fname) { |
2205 | 0 | switch (i) { |
2206 | 0 | case '/': case ':': case '[': case ']': i='_'; break; |
2207 | 0 | default: break; |
2208 | 0 | } |
2209 | 0 | } |
2210 | 0 | fname = "." + fname + ".txn"; |
2211 | 0 | this->fd = ::open(fname.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); |
2212 | 0 |
|
2213 | 0 | if (this->fd < 0) { |
2214 | 0 | throw std::runtime_error("Unable to open transaction persistence file: " + fname + " (" + std::string(strerror(errno)) + ")"); |
2215 | 0 | } |
2216 | 0 |
|
2217 | 0 | struct stat sb; |
2218 | 0 | if (::fstat(this->fd, &sb) < 0) { |
2219 | 0 | close(this->fd); |
2220 | 0 | throw std::runtime_error("Failed to stat transaction persistence file: " + fname + " (" + std::string(strerror(errno)) + ")"); |
2221 | 0 | } |
2222 | 0 | truncMapFile(sb.st_size == 0 ? /*1MB*/1048576 : sb.st_size); |
2223 | 0 | } |
2224 | | |
2225 | | // a non-persistent transaction over constant data (just used by remote consumers) |
2226 | 6 | Transaction(const uint8_t* d, size_t len) : fd(-1), file_size(len), map_size(len), data(const_cast<uint8_t*>(d)), data_size(nullptr), wi(0), ri(0) { |
2227 | 6 | this->data_size = &this->map_size; |
2228 | 6 | } |
2229 | | |
2230 | 1.49M | bool canRead(size_t x) const { |
2231 | | // ri+x (rather than x <= data_size-ri) would overflow and wrap back |
2232 | | // below data_size when x is huge -- notably when a caller has cast a |
2233 | | // negative, untrusted count to size_t (STRFR-433920). ri is always |
2234 | | // <= *data_size here: it only advances via skip(), which callers may |
2235 | | // only invoke after a canRead() that already bounded the advance. |
2236 | 1.49M | return x <= (*this->data_size - this->ri); |
2237 | 1.49M | } |
2238 | | |
2239 | 1.09M | const uint8_t* ptr() const { |
2240 | 1.09M | return this->data + this->ri; |
2241 | 1.09M | } |
2242 | | |
2243 | 1.09M | void skip(size_t d) { |
2244 | 1.09M | this->ri += d; |
2245 | 1.09M | } |
2246 | | |
2247 | | // Returns the value, not a pointer into the stream. The cursor sits wherever |
2248 | | // the preceding fields left it, so a T* formed from it is only as aligned as |
2249 | | // that offset happened to be, and reading through such a pointer is |
2250 | | // undefined -- it faults outright on targets that require alignment, and |
2251 | | // sanitizers report it on the ones that do not. Copying the bytes out has |
2252 | | // the same cost here and cannot be misused that way. |
2253 | | // |
2254 | | // The caller still bounds the read itself: canRead(sizeof(T)) first, as the |
2255 | | // dispatch loops in bin/hog/session.C do. |
2256 | | template <typename T> |
2257 | 412k | T read() { |
2258 | 412k | static_assert(std::is_trivially_copyable<T>::value, |
2259 | 412k | "Transaction::read copies raw bytes out of the stream"); |
2260 | 412k | T v; |
2261 | 412k | memcpy(&v, ptr(), sizeof(T)); |
2262 | 412k | skip(sizeof(T)); |
2263 | 412k | return v; |
2264 | 412k | } |
2265 | | |
2266 | 0 | size_t size() const { |
2267 | 0 | return *this->data_size; |
2268 | 0 | } |
2269 | | |
2270 | 0 | void clear() { |
2271 | 0 | *this->data_size = 0; |
2272 | 0 | this->wi = 0; |
2273 | 0 | this->ri = 0; |
2274 | 0 | } |
2275 | | |
2276 | 0 | bool readToCompletion(rpipe& p, size_t timeoutNS, const std::function<void()>& timeoutF) { |
2277 | 0 | static const size_t blockSize = 1024; |
2278 | 0 |
|
2279 | 0 | if (this->wi + blockSize >= this->file_size) { |
2280 | 0 | truncMapFile(this->file_size + /*1MB*/1048576); |
2281 | 0 | } |
2282 | 0 |
|
2283 | 0 | uint8_t txnFlag = 0; |
2284 | 0 | this->wi += p.read(this->data + this->wi, blockSize, &txnFlag, timeoutNS, timeoutF); |
2285 | 0 | *this->data_size = this->wi; |
2286 | 0 |
|
2287 | 0 | switch (txnFlag) { |
2288 | 0 | case PRIV_HSTORE_PAGE_STATE_ROLLBACK: |
2289 | 0 | clear(); |
2290 | 0 | return false; |
2291 | 0 | case PRIV_HSTORE_PAGE_STATE_COMMIT: { |
2292 | 0 | return true; |
2293 | 0 | } |
2294 | 0 | default: |
2295 | 0 | return false; |
2296 | 0 | } |
2297 | 0 | } |
2298 | | private: |
2299 | | int fd; |
2300 | | size_t file_size; |
2301 | | size_t map_size; |
2302 | | |
2303 | | uint8_t* data; |
2304 | | size_t* data_size; |
2305 | | size_t wi; |
2306 | | size_t ri; |
2307 | | |
2308 | 0 | void truncMapFile(size_t sz) { |
2309 | 0 | off_t dsz = sz - this->file_size; |
2310 | 0 | int r = ::posix_fallocate(this->fd, this->file_size, dsz); |
2311 | 0 | if (r != 0) { |
2312 | 0 | if (r == ENOSPC) { |
2313 | 0 | throw std::runtime_error("Failed to expand transaction persistence file (no space available)"); |
2314 | 0 | } else { |
2315 | 0 | throw std::runtime_error("Failed to expand transaction persistence file"); |
2316 | 0 | } |
2317 | 0 | } |
2318 | 0 | this->file_size = sz; |
2319 | 0 |
|
2320 | 0 | if (sz > this->map_size) { |
2321 | 0 | if (this->data != nullptr) { ::munmap(this->data-sizeof(size_t), this->map_size); } |
2322 | 0 |
|
2323 | 0 | this->map_size = align<size_t>(sz, /*10MB*/10485760); |
2324 | 0 | this->data = reinterpret_cast<uint8_t*>(mmap(nullptr, this->map_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, this->fd, 0)); |
2325 | 0 | if (this->data == MAP_FAILED) { |
2326 | 0 | throw std::runtime_error("Failed to map transaction persistence file"); |
2327 | 0 | } |
2328 | 0 | this->data_size = reinterpret_cast<size_t*>(this->data); |
2329 | 0 | this->data += sizeof(size_t); |
2330 | 0 | } |
2331 | 0 | } |
2332 | | }; |
2333 | | |
2334 | 0 | [[noreturn]] inline void runReadProcessWithTimeout(const QueueConnection& qc, const WaitPolicy wp, const std::function<std::function<void(Transaction&)>(PipeQOS, CommitMethod, const statements&)>& initF, size_t timeoutNS, const std::function<void(const reader&)>& userTimeoutF) { |
2335 | 0 | Transaction txn(qc.shmname); |
2336 | 0 | reader rd(qc, wp); |
2337 | 0 | rpipe p(&rd); |
2338 | 0 |
|
2339 | 0 | // initialize |
2340 | 0 | reader::MetaData md = rd.meta(); |
2341 | 0 |
|
2342 | 0 | uint32_t hstoreVersion = 0; |
2343 | 0 | size_t o = r(md, 0, &hstoreVersion); |
2344 | 0 | if (hstoreVersion != HSTORE_VERSION) { |
2345 | 0 | throw std::runtime_error("Can't read storage data from incompatible process"); |
2346 | 0 | } |
2347 | 0 |
|
2348 | 0 | // read group flags/settings |
2349 | 0 | int qos=0, cm=0; |
2350 | 0 | o = r(md, o, &qos); |
2351 | 0 | o = r(md, o, &cm); |
2352 | 0 |
|
2353 | 0 | // read all storage statements |
2354 | 0 | uint32_t n = 0; |
2355 | 0 | o = r(md, o, &n); |
2356 | 0 | statements ss; |
2357 | 0 | ss.reserve(n); |
2358 | 0 | for (size_t i = 0; i < n; ++i) { |
2359 | 0 | statement s; |
2360 | 0 | o = rs(md, o, &s.name); |
2361 | 0 | o = r (md, o, &s.flags); |
2362 | 0 | o = rs(md, o, &s.fmtstr); |
2363 | 0 | o = rs(md, o, &s.file); |
2364 | 0 | o = r (md, o, &s.line); |
2365 | 0 | o = r (md, o, &s.id); |
2366 | 0 | o = rs(md, o, &s.type); |
2367 | 0 | ss.push_back(s); |
2368 | 0 | } |
2369 | 0 |
|
2370 | 0 | auto txnF = initF(static_cast<PipeQOS>(qos), static_cast<CommitMethod>(cm), ss); |
2371 | 0 |
|
2372 | 0 | auto timeoutF = [&rd, userTimeoutF]() { |
2373 | 0 | userTimeoutF(rd); |
2374 | 0 | }; |
2375 | 0 |
|
2376 | 0 | // read transactions and call back into user code |
2377 | 0 | while (true) { |
2378 | 0 | if (txn.readToCompletion(p, timeoutNS, timeoutF)) { |
2379 | 0 | txnF(txn); |
2380 | 0 | txn.clear(); |
2381 | 0 | } |
2382 | 0 | } |
2383 | 0 | } |
2384 | | |
2385 | 0 | [[noreturn]] inline void runReadProcess(const QueueConnection& qc, const WaitPolicy wp, const std::function<std::function<void(Transaction&)>(PipeQOS, CommitMethod, const statements&)>& initF) { |
2386 | 0 | runReadProcessWithTimeout(qc, wp, initF, 0, [](const reader&){}); |
2387 | 0 | } |
2388 | | |
2389 | | }} |
2390 | | |
2391 | | #endif |