Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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