src/dxapi/native/tickdb/common.h (351 lines of code) (raw):

/* * Copyright 2021 EPAM Systems, Inc * * See the NOTICE file distributed with this work for additional information * regarding copyright ownership. Licensed under the Apache License, * Version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ #pragma once #define _CRT_SECURE_NO_WARNINGS #include "config.h" #ifndef DBG_LOG_ENABLED #define DBG_LOG_ENABLED 1 #endif #ifndef LOG_FILENAME #define LOG_FILENAME "dxapi_log.txt" #endif // TODO: MAYBE: support for old Visual Studio versions with broken stdint? #include "dxapi/dxcommon.h" #include "platform/platform.h" #include "util/qpc_timer.h" #include "util/charbuffer.h" #include <assert.h> #include <stdlib.h> #include <stdint.h> #include <sstream> #include <algorithm> #include <vector> #include <exception> // C++11! #include <type_traits> namespace DxApiImpl { template<typename T> INLINE std::string toString(const T &x); template<typename T> std::string toString(const std::vector<T> &x) { std::string out; const char * sep = ""; for (auto &i : x) { out.append(sep).append(toString(i)); sep = ", "; } return out; } template<> std::string INLINE toString<bool>(const bool &x) { return x ? "true" : "false"; } template<> std::string INLINE toString<std::string>(const std::string &x) { return x; } template<> std::string INLINE toString<const char *>(const char * const &x) { return std::string(x); } /*template<> std::string INLINE toString<DxApi::Nullable<std::string>>(const DxApi::Nullable<std::string> &x) { return x.is_null() ? std::string("<NULL>") : x.get(); }*/ template<typename T> std::string INLINE toString(const T &x) { std::stringstream ss; ss << x; return ss.str(); } //#include "util/logger.h" } #ifdef _countof #define COUNTOF(arr) _countof(arr) #else #define COUNTOF(arr) (sizeof(arr) / sizeof(arr[0])) #endif // number of bits used to hold a type, assuming byte is 8 bits #define BITSIZEOF(x) (8 * sizeof(x)) // Fill POD structure with 0 #define DX_CLEAR(What) memset(&(What), 0, sizeof(What) + 0 * 1/(int)(sizeof(What) != sizeof(void *))) // Fill POD structure with 0, using non-inlined memset call #define DX_CLEAR_NI(What) memset_ni(&(What), 0, sizeof(What) + 0 * 1/(int)(sizeof(What) != sizeof(void *))) /** * @brief Rounds down the given value to the alignment boundary. */ #define DX_ALIGN(Value, Alignment) \ ((uintptr_t)(Value) & ~((uintptr_t)(Alignment) - 1)) /** * @brief Rounds down the given value to the sizeof of specific type */ #define DX_ALIGN_TO(Value, Type) \ ((uintptr_t)(Value) & ~((uintptr_t)sizeof(Type) - 1)) /** * @brief Rounds up the given value to the alignment boundary. */ #define DX_ALIGN_UP(Value, Alignment) \ (((uintptr_t)(Value) + ((uintptr_t)(Alignment) - 1)) & ~((uintptr_t)(Alignment) - 1)) /** * @brief Rounds down the given pointer to the alignment boundary. */ #define DX_POINTER_ALIGN(Pointer, Alignment) \ ((void *)DX_ALIGN(Pointer, Alignment)) /** * @brief Rounds up the given pointer to the alignment boundary. */ #define DX_POINTER_ALIGN_UP(Pointer, Alignment) \ ((void *)DX_ALIGN_UP(Pointer, Alignment)) // generate bitmask with N low bits set (0..01..1) #define BITMASK_L(num_low_bits) ((uint64_t)(-1) >> (64 - (num_low_bits))) // generate bitmask with N high bits set (1..10..0) #define BITMASK_H(num_high_bits) ((uint64_t)(-1) << (64 - (num_high_bits))) // generate bitmask with N * 8 low bits set (0x00..00FF..FF) #define BYTEMASK_L(num_low_bytes) BITMASK_L((num_low_bytes) * 8) // generate bitmask with N * 8 high bits set (0xFF..FF00..00) #define BYTEMASK_H(num_high_bytes) BITMASK_H((num_high_bytes) * 8) // Effective read buffer size slightly smaller than 4 pages so the last read doesn't go into a new one #define READ_BUFFER_SIZE (0x4000 - 8 * 2) // Buffer size to actually allocate #define READ_BUFFER_ALLOC_SIZE (READ_BUFFER_SIZE + 8 * 2/*front&back guard areas*/ + 0xFFF/* page alignment */ + 128 /* cache line - sized guard area*/) #define WRITE_BUFFER_ALLOC_SIZE (MAX_MESSAGE_SIZE + 0xFFF + 8 * 2 /* for chunk header and guard area */ + 128 /* extra cache line */) #define CRLF "\r\n" #define CRLFCRLF "\r\n\r\n" #define forn(i, n) for (intptr_t i = 0, i##_count = (intptr_t)(n); i != i##_count; ++i) // Iterate through range [n .. 0), n assumed to be <=0 #define for_neg(i, n) assert(n <= 0); for (intptr_t i = (n); 0 != i; ++i) template<typename T> T INLINE align(T x, uintptr_t y) { return (T)DX_ALIGN(x, y); } // Non-inlined library functions, to be used for code size savings where necessary size_t strlen_ni(const char * str); int strcmp_ni(const char * str0, const char * str1); void * memset_ni(void * data, int value, size_t size); #if defined(_MSC_VER) && (_MSC_VER < 1900 ) // Workaround for snprintf on VS2013 or less. _MSC_VER 1900 = Toolset 14 // NOTE: snprintf and _snprintf are not fully compatible though this doesn't matter in our case #define snprintf _snprintf #endif #if !defined(_MSC_VER) && !DX_PLATFORM_64 #define DXAPI_CALLBACK #else #define DXAPI_CALLBACK __stdcall #endif #define VA_DELEGATE(va,param,f) do { va_list va; va_start(va, param); (f); va_end(va); } while(0) static int memcmp_impl(const void * a, const void * b, size_t asize, size_t bsize) { return asize < bsize ? -1 : asize > bsize ? 1 : 0 == asize ? 0 : memcmp(a, b, asize); } /** * Compare 2 strings by directly comparing bytes of the content */ template<typename T> INLINE static int memcmp(const std::basic_string<T> &a, const std::basic_string<T> &b) { return memcmp_impl(a.data(), b.data(), a.size() * sizeof(T), b.size() * sizeof(T)); } /** * Compare 2 arrays by directly comparing bytes of the content */ template<typename T> INLINE static int memcmp(const std::vector<T> &a, const std::vector<T> &b) { return memcmp_impl(a.data(), b.data(), a.size() * sizeof(T), b.size() * sizeof(T)); } /** * Calculate CRC32 of a buffer */ uint32_t crc32(const uint8_t * buffer, uintptr_t len); INLINE uint32_t crc32(const std::vector<uint8_t> &buffer) { return crc32(buffer.data(), buffer.size()); } INLINE uint32_t crc32(const std::string &buffer) { return crc32((const uint8_t *)buffer.data(), buffer.size()); } /** * Return true if string s has specified prefix */ bool strhasprefix(const char * s, const char * prefix); bool strhasprefix(const char * s, const char * prefix, size_t prefixlength); bool strhasprefix(const char * s, size_t string_length, const char * prefix, size_t prefixlength); /** * Split source string into up to n strings, using the specified separator character, by copying them into an array of std::string * Returns the number of strings created */ size_t split(std::string strings[], size_t nstrings, const char * src, char separator); extern bool dbg_log_enabled; std::string dbg_logfile_path(const char * name, const char * ext); void dbg_log(const char * fmt, ...); // Log data to file, and, optionally, put it into out std::string. 'out' parameter is nullable void dbg_log(std::string * out, const char * fmt, ...); void dbg_dump(const char * basename, void * data, size_t nBytes); void format_string(std::string * out, const char * fmt, ...); // If offset > out->size(), offset = out->size() (append) void format_string(std::string * out, size_t offset, const char * fmt, ...); void format_string(std::string * out, size_t offset, va_list); void format_string(std::string * out, va_list); void dbg_save(const char * filename); bool load_file(std::vector<uint8_t> &out, const char * filename); bool save_file(const std::vector<uint8_t> &in, const char * filename); // TODO: move std::string timestamp_cursor2str(int64_t x); std::string timestamp_ns2str(int64_t x, bool withSeparator); #define TS_CURSOR2STR(x) (timestamp_cursor2str(x).c_str()) #define TS_NS2STR(x) (timestamp_ns2str(x, true).c_str()) // Return vector.data() for non-empty vectors or dummy non-NULL pointer for empty vectors template<typename T> INLINE static const T * data_ptr(const std::vector<T> &v) { auto p = v.data(); return NULL != p ? p : (const T *)(0x10); } static_assert(sizeof(long long unsigned) == sizeof(uint64_t), "ulonglong != uint64"); static_assert(sizeof(unsigned) == sizeof(uint32_t), "uint != uint32"); #define FMT_HEX64 "0x%016llX" #define FMT_HEX32 "0x%08X" #if defined(DX_PLATFORM_64) #define FMT_PTR FMT_HEX64 #else #define FMT_PTR FMT_HEX32 #endif // In Visual Studio __FUNCTION__ gives function name with enclosing namespaces // __func__ is char[] identifier #ifdef _MSC_VER #define _FUNCTION_ __FUNCTION__ #define COMMA_VA_ARGS(...) , __VA_ARGS__ #else // gcc, clang, etc. #define _FUNCTION_ __PRETTY_FUNCTION__ //#define _FUNCTION_ __func__ #define COMMA_VA_ARGS(...) , ##__VA_ARGS__ #endif #if DBG_LOG_ENABLED != 1 #define DBGLOG (void) // Still format te mesasage into outpput string, but do not log this string. #define DBGLOGERR(OUT, FMT, ...) format_string(OUT, FMT " at %s() in " __FILE__ ":%u" COMMA_VA_ARGS(__VA_ARGS__), _FUNCTION_, __LINE__) //#define dx_assert(_EXPR, ...) assert(_EXPR) #define dx_assert(_EXPR, FMT, ...) (void)((!!(_EXPR)) \ || (DBG_IS_DEBUGGER_PRESENT() ? (DBG_BREAK(), 1) : 0) \ || (_wassert(_CRT_WIDE(#_EXPR), _CRT_WIDE(__FILE__), __LINE__), 0)) #else #define DBGLOG if (dbg_log_enabled) dbg_log #define DBGLOGERR(OUT, FMT, ...) if (dbg_log_enabled) dbg_log(OUT, FMT " at %s() in " __FILE__ ":%u" COMMA_VA_ARGS(__VA_ARGS__), _FUNCTION_, __LINE__) #ifdef _MSC_VER #define dx_assert(_EXPR, FMT, ...) (void)((!!(_EXPR)) \ || (dbg_log_enabled ? (dbg_log(FMT " ASSERT:(%s) at %s() in " __FILE__ ":%u" COMMA_VA_ARGS(__VA_ARGS__), #_EXPR, _FUNCTION_, __LINE__), 0) : 0 ) \ || (DBG_IS_DEBUGGER_PRESENT() ? (DBG_BREAK(), 1) : 0) \ || (_wassert(_CRT_WIDE(#_EXPR), _CRT_WIDE(__FILE__), __LINE__), 0)) #else #define dx_assert(_EXPR, FMT, ...) (void)((!!(_EXPR)) \ || (dbg_log_enabled ? (dbg_log(FMT " ASSERT:(%s) at %s() in " __FILE__ ":%u" COMMA_VA_ARGS(__VA_ARGS__), #_EXPR, _FUNCTION_, __LINE__), 0) : 0 ) \ || (DBG_IS_DEBUGGER_PRESENT() ? (DBG_BREAK(), 1) : 0) \ || (assert(_EXPR), 0)) #endif #endif #define THROW_DBGLOG_EX(EXC, FMT, ...) do { \ std::string out; DBGLOGERR(&out, FMT, __VA_ARGS__); \ throw EXC(out); \ } while (0) #define THROW_DBGLOG_IO(EXC, ERR, FMT, ...) do { \ std::string out; DBGLOGERR(&out, FMT, __VA_ARGS__); \ throw EXC(ERR, out); \ } while (0) #define THROW_DBGLOG(FMT, ...) THROW_DBGLOG_EX(std::runtime_error, FMT, __VA_ARGS__) #ifdef NDEBUG #define _wassert (void) #endif /** * Common macro to dispatch event handler callback from native impl. Callbacks may throw exceptions which will be caught and logged */ #define DXAPIIMPL_DISPATCH_THROW() throw; // #define DXAPIIMPL_DISPATCH_THROW #define DXAPIIMPL_DISPATCH_LOG() do { DBGLOG("%s", errmsg.c_str()); puts(errmsg.c_str()); } while(0) // Dispatch event handler callback, with appropriate logging etc. #define DXAPIIMPL_DISPATCH(from, callback, parms, textid) do { try { \ auto cb = callback; \ if (NULL != cb) { \ DBGLOG_VERBOSE(LOGHDR "." #from "(): dispatch " #callback " STARTED", textid); \ cb parms; \ DBGLOG_VERBOSE(LOGHDR "." #from "(): dispatch " #callback " FINISHED", textid); \ } \ } \ catch (const std::exception &e) { \ std::string errmsg; \ format_string(&errmsg, LOGHDR "." #from "(): dispatch " #callback " ERROR: std::exception: %s", textid, e.what()); \ DXAPIIMPL_DISPATCH_LOG(); \ DXAPIIMPL_DISPATCH_THROW(); \ } \ catch (...) { \ std::string errmsg; \ format_string(&errmsg, LOGHDR "." #from "(): dispatch " #callback " ERROR: system exception!", textid); \ DXAPIIMPL_DISPATCH_LOG(); \ DXAPIIMPL_DISPATCH_THROW(); \ } \ } while (0) /** * In-place substring replacement. Replace all occurences of <from> with <to>, length should match. Case sensitive. */ void replace_all(char * s, size_t s_size, const char * from, const char * to); INLINE void replace_all(std::string * const s, const char * from, const char * to) { if (NULL == s) throw std::invalid_argument("replace destination is NULL"); return replace_all(&(*s)[0], s->size(), from, to); } // This is very hacky, but works /** * Copy src to dest while replacing one substring with another. Case sensitive. */ void replace_all(std::string * const dest, const char * src, const char * from, const char * to); INLINE void replace_all(std::string * const dest, const std::string &src, const char * from, const char * to) { return replace_all(dest, src.c_str(), from, to); } /** * Fill contents of stl array with 0 without resizing it */ template <typename T> INLINE void clear(std::vector<T> &v) { size_t l = v.size(); if (0 != l) { memset(&v[0], 0, l); } } INLINE void clear(std::string &v) { size_t l = v.size(); if (0 != l) { memset(&v[0], 0, l); } } INLINE void safe_clear(std::string &v) { for (auto &i : v) (volatile char &)i = 0; } /** * Scramble contents of a buffer by XOR-ing it with simple LCG */ template <typename T> INLINE void scramble(T * v, size_t size) { uint32_t x = (uint32_t)(size * 9 + 103); forn (i, size) { x = x * 1664525U + 1013904223U; v[i] ^= (T)(x >> 16); } } template <typename T> INLINE void scramble(std::vector<T> &v) { size_t l = v.size(); if (0 != l) scramble(&v[0], l); } INLINE void scramble(std::string &v) { size_t l = v.size(); if (0 != l) scramble(&v[0], l); } // Clears itself before deletion class secure_string : public std::string { public: INLINE secure_string() : std::string() {} INLINE secure_string(const std::string &s) : std::string(s) {} INLINE ~secure_string() { safe_clear(*this); resize(0); } }; /* Range checking helpers, for doing explicit unsigned checks */ static INLINE bool below(unsigned a, unsigned b) { return a < b; } static INLINE bool below(uint64_t a, uint64_t b) { return a < b; } /* true if a <= x < b */ static INLINE bool in_range(uint32_t x, uint32_t a, uint32_t b) { return below((x - a), (b - a)); } static INLINE bool in_range(int32_t x, int32_t a, int32_t b) { return below((uint32_t)(x - a), (uint32_t)(b - a)); } static INLINE bool in_range(uint64_t x, uint64_t a, uint64_t b) { return below((x - a), (b - a)); } static INLINE bool in_range(int64_t x, int64_t a, int64_t b) { return below((uint64_t)(x - a), (uint64_t)(b - a)); } #if defined (__APPLE__) static INLINE bool below(size_t a, size_t b) { return a < b; } static INLINE bool in_range(size_t x, size_t a, size_t b) { return below((x - a), (b - a)); } #endif // Static cast to unsigned version of type T //template<class T> INLINE make_unsigned<T>::type unsigned_cast(T x) { typedef make_unsigned<T>::type Q; return static_cast<Q>(x); } //template<class T> INLINE make_signed<T>::type signed_cast(T x) { return static_cast<make_signed<T>::type>(x); } // check for divisibility of signed integer by unsigned constant template<typename P, typename Q> static INLINE bool fast_divisible_by(P divided, Q divisor) { typedef typename std::make_unsigned<P>::type U_P; typedef typename std::make_unsigned<Q>::type U_Q; assert(divisor > 0 && "divisor must be > 0"); return 0 == (divided + (((U_P)(-1) / 2) / U_Q(divisor)) * divisor) % (U_Q)divisor; } // divide signed integer by unsigned constant with flooring, instead of truncating towards 0 template<typename P, typename Q> static INLINE P fast_divide_by(P divided, Q divisor) { typedef typename std::make_unsigned<P>::type U_P; typedef typename std::make_unsigned<Q>::type U_Q; assert(divisor > 0 && "divisor must be > 0"); return (P)((divided + (((U_P)(-1) / 2) / U_Q(divisor)) * divisor) / (U_Q)divisor - ((U_P)(-1) / 2) / U_Q(divisor)); } template<typename INTTYPE /* underlying type */, class ENUM /* 'enum' class type */, unsigned N, const char ** typeInfo, bool HAS_FROM_CHAR> struct EnumHelper { INLINE ENUM fromChar(int x) const { return HAS_FROM_CHAR ? ((ENUM)(0x40 == (x & ~0x3F) ? fromAsciiChar64[(uintptr)(unsigned)x - 0x40] : ~(INTTYPE)0)) : (ENUM)x; } INLINE char toChar(const ENUM &x) const { return HAS_FROM_CHAR ? (x < N ? *typeInfo[(unsigned)x] : '\0') : x; } INLINE const char * toString(ENUM x) const { return x < N ? typeInfo[(unsigned)x] + 2 * (int)HAS_FROM_CHAR : NULL; } INTTYPE fromString(const char s[]) const { for (intptr i = 0; i < N; ++i) { if (0 == strcmp(typeInfo[i] + 2 * (int)HAS_FROM_CHAR, s)) { return (INTTYPE)i; } } return ~(INTTYPE)0; } EnumHelper() { if (HAS_FROM_CHAR) { memset_ni(fromAsciiChar64, ~0, sizeof(fromAsciiChar64)); for (intptr i = N - 1; i >= 0; --i) { fromAsciiChar64[*typeInfo[i] - 0x40] = (INTTYPE)i; } } } private: INTTYPE fromAsciiChar64[1 + 63 * (int)HAS_FROM_CHAR]; }; #define DX_ENUM_HELPER(INTTYPE, NS, ENUM, HAS_FROM_CHAR) EnumHelper<INTTYPE, NS ENUM::Enum, COUNTOF(info##ENUM), info##ENUM, HAS_FROM_CHAR> #if 0 #define DX_ENUM_HELPER_IMPL(INTTYPE, NS, CLASS, HAS_FROM_CHAR) \ { CLASS##EnumHelper(); } ___g_enumHelper##CLASS; \ \ template<> NS CLASS::EnumClass(const char s[]) : value_(___g_enumHelper##CLASS.fromString(s)) {} \ template<> NS CLASS NS CLASS::fromChar(int x) { return ___g_enumHelper##CLASS.fromChar(x); } \ template<> char NS CLASS::toChar() const { return ___g_enumHelper##CLASS.toChar(*this); } \ template<> const char* NS CLASS::toString() const { return ___g_enumHelper##CLASS.toString(*this); } \ /* Constructor */ CLASS##EnumHelper::CLASS##EnumHelper() #endif #if 0 #define DX_ENUM_HELPER_IMPL(INTTYPE, NS, CLASS, HAS_FROM_CHAR) \ { CLASS##EnumHelper(); } ___g_enumHelper##CLASS; \ \ NS CLASS::EnumClass(const char s[]) : value_(___g_enumHelper##CLASS.fromString(s)) {} \ NS CLASS NS CLASS::fromChar(int x) { return ___g_enumHelper##CLASS.fromChar(x); } \ char NS CLASS::toChar() const { return ___g_enumHelper##CLASS.toChar(*this); } \ const char* NS CLASS::toString() const { return ___g_enumHelper##CLASS.toString(*this); } \ /* Constructor */ CLASS##EnumHelper::CLASS##EnumHelper() #else #define DX_ENUM_HELPER_IMPL(INTTYPE, NS, ENUM, HAS_FROM_CHAR) \ { ENUM##EnumHelper(); } ___g_enumHelper##ENUM; \ \ NS ENUM::ENUM(const char s[]) : EnumClass((Enum)___g_enumHelper##ENUM.fromString(s)) {} \ NS ENUM NS ENUM::fromChar(int x) { return ___g_enumHelper##ENUM.fromChar(x); } \ char NS ENUM::toChar() const { return ___g_enumHelper##ENUM.toChar(*this); } \ const char* NS ENUM::toString() const { return ___g_enumHelper##ENUM.toString(*this); } \ \ /* Constructor for the helper */ ENUM##EnumHelper::ENUM##EnumHelper() #endif // This macro takes enum underlying type, enum name, boolean flag if support for toChar/fromChar is necessary (changes infoXX format) #define IMPLEMENT_ENUM(INTTYPE, ENUM, HAS_FROM_CHAR) \ static struct ENUM##EnumHelper : DX_ENUM_HELPER(INTTYPE,, ENUM, HAS_FROM_CHAR) \ DX_ENUM_HELPER_IMPL(INTTYPE,, ENUM, HAS_FROM_CHAR) #define IMPLEMENT_CLASS_ENUM(INTTYPE, NS, ENUM, HAS_FROM_CHAR) \ static struct ENUM##EnumHelper : DX_ENUM_HELPER(INTTYPE, NS::, ENUM, HAS_FROM_CHAR) \ DX_ENUM_HELPER_IMPL(INNER, NS::, ENUM, HAS_FROM_CHAR) // Defines a set of functions for accessing internal implementation class #define DEFINE_IMPL_CLASS(EXTERNAL, INTERNAL) \ static INLINE const INTERNAL* impl(const EXTERNAL *x) { return static_cast<const INTERNAL*>(x); } \ static INLINE const INTERNAL& impl(const EXTERNAL &x) { return static_cast<const INTERNAL&>(x); } \ static INLINE INTERNAL* impl(EXTERNAL *x) { return static_cast<INTERNAL*>(x); } \ static INLINE INTERNAL& impl(EXTERNAL &x) { return static_cast<INTERNAL&>(x); } #include "protocol.h" #define _DXAPI