From fd6423dedf2c2055d124850fc188fa9aaa95ff1c Mon Sep 17 00:00:00 2001 From: Arseny Smirnov Date: Wed, 24 Jun 2020 14:47:36 +0300 Subject: [PATCH] update tdutils from another project GitOrigin-RevId: 79b8eb2ba229d733f82dbb17b8bd7f27471c9472 --- td/telegram/StorageManager.cpp | 2 +- td/telegram/files/FileStatsWorker.cpp | 2 +- tdutils/CMakeLists.txt | 37 ++- tdutils/generate/CMakeLists.txt | 4 + tdutils/td/utils/BigNum.cpp | 10 +- tdutils/td/utils/BufferedFd.h | 6 + tdutils/td/utils/CancellationToken.h | 5 + tdutils/td/utils/FileLog.cpp | 17 +- tdutils/td/utils/FileLog.h | 5 + tdutils/td/utils/Heap.h | 4 + tdutils/td/utils/JsonBuilder.h | 22 +- tdutils/td/utils/MpmcQueue.h | 18 +- tdutils/td/utils/MpmcWaiter.h | 267 ++++++++++++++++-- tdutils/td/utils/OptionParser.cpp | 6 + tdutils/td/utils/OptionParser.h | 4 + tdutils/td/utils/PathView.cpp | 19 ++ tdutils/td/utils/PathView.h | 4 + tdutils/td/utils/Random.cpp | 26 +- tdutils/td/utils/Random.h | 18 ++ tdutils/td/utils/SharedObjectPool.h | 7 +- tdutils/td/utils/SharedSlice.cpp | 1 + tdutils/td/utils/SharedSlice.h | 5 +- tdutils/td/utils/Slice.cpp | 1 + tdutils/td/utils/Span.h | 52 +++- tdutils/td/utils/Status.h | 99 ++++++- tdutils/td/utils/StringBuilder.h | 2 + tdutils/td/utils/ThreadLocalStorage.h | 3 +- tdutils/td/utils/ThreadSafeCounter.h | 16 +- tdutils/td/utils/Time.cpp | 22 ++ tdutils/td/utils/Time.h | 17 +- tdutils/td/utils/TimedStat.h | 24 ++ tdutils/td/utils/Timer.cpp | 31 +- tdutils/td/utils/Timer.h | 10 +- tdutils/td/utils/TsFileLog.cpp | 45 +-- tdutils/td/utils/TsFileLog.h | 10 +- tdutils/td/utils/TsList.h | 1 + tdutils/td/utils/Variant.h | 4 + tdutils/td/utils/VectorQueue.h | 9 + tdutils/td/utils/base64.cpp | 62 ++++ tdutils/td/utils/base64.h | 4 +- tdutils/td/utils/bits.h | 37 +++ tdutils/td/utils/crypto.cpp | 10 +- tdutils/td/utils/crypto.h | 12 +- tdutils/td/utils/filesystem.cpp | 41 ++- tdutils/td/utils/filesystem.h | 13 +- tdutils/td/utils/format.h | 5 + tdutils/td/utils/invoke.h | 4 +- tdutils/td/utils/logging.cpp | 23 ++ tdutils/td/utils/logging.h | 39 ++- tdutils/td/utils/misc.h | 27 +- tdutils/td/utils/optional.h | 9 + tdutils/td/utils/port/FileFd.cpp | 11 + tdutils/td/utils/port/MemoryMapping.cpp | 2 +- tdutils/td/utils/port/StdStreams.cpp | 9 +- tdutils/td/utils/port/detail/Epoll.cpp | 1 + .../td/utils/port/detail/ThreadPthread.cpp | 1 + tdutils/td/utils/port/detail/skip_eintr.h | 1 + tdutils/td/utils/port/rlimit.cpp | 80 ++++-- tdutils/td/utils/port/rlimit.h | 7 +- tdutils/td/utils/port/signals.cpp | 3 + tdutils/td/utils/port/user.cpp | 1 + tdutils/td/utils/port/user.h | 3 +- tdutils/td/utils/tests.cpp | 9 +- tdutils/td/utils/tests.h | 60 ++-- tdutils/td/utils/tl_helpers.h | 17 +- tdutils/td/utils/tl_parsers.h | 10 + tdutils/td/utils/tl_storers.h | 14 + tdutils/td/utils/uint128.h | 13 +- tdutils/test/ConcurrentHashMap.cpp | 2 +- tdutils/test/List.cpp | 1 + tdutils/test/MpmcWaiter.cpp | 46 ++- tdutils/test/OptionParser.cpp | 17 +- tdutils/test/SharedSlice.cpp | 1 + tdutils/test/heap.cpp | 10 +- tdutils/test/log.cpp | 12 +- tdutils/test/misc.cpp | 92 ++++-- test/main.cpp | 4 +- 77 files changed, 1295 insertions(+), 253 deletions(-) diff --git a/td/telegram/StorageManager.cpp b/td/telegram/StorageManager.cpp index 270d778bc..3de3ed427 100644 --- a/td/telegram/StorageManager.cpp +++ b/td/telegram/StorageManager.cpp @@ -340,7 +340,7 @@ void StorageManager::schedule_next_gc() { if (next_gc_at > sys_time + GC_EACH) { next_gc_at = sys_time + GC_EACH; } - next_gc_at += Random::fast(GC_DELAY, GC_DELAY + GC_RAND_DELAY); + next_gc_at += Random::fast(static_cast(GC_DELAY), static_cast(GC_DELAY + GC_RAND_DELAY)); CHECK(next_gc_at >= sys_time); auto next_gc_in = next_gc_at - sys_time; diff --git a/td/telegram/files/FileStatsWorker.cpp b/td/telegram/files/FileStatsWorker.cpp index 874028447..2b757d0d0 100644 --- a/td/telegram/files/FileStatsWorker.cpp +++ b/td/telegram/files/FileStatsWorker.cpp @@ -102,7 +102,7 @@ struct FsFileInfo { template void scan_fs(CancellationToken &token, CallbackT &&callback) { for (int32 i = 0; i < MAX_FILE_TYPE; i++) { - int32 main_file_type = static_cast(get_main_file_type(static_cast(i))); + int32 main_file_type = static_cast(get_main_file_type(static_cast(i))); if (i != main_file_type) { continue; } diff --git a/tdutils/CMakeLists.txt b/tdutils/CMakeLists.txt index 38a10bf3f..4f7928d47 100644 --- a/tdutils/CMakeLists.txt +++ b/tdutils/CMakeLists.txt @@ -1,5 +1,15 @@ cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +option(TDUTILS_MIME_TYPE "Generate mime types conversion (gperf is required)" ON) + +if (WIN32) + if (WINGETOPT_FOUND) + set(TD_HAVE_GETOPT 1) + endif() +else() + set(TD_HAVE_GETOPT 1) +endif() + if (NOT DEFINED CMAKE_INSTALL_LIBDIR) set(CMAKE_INSTALL_LIBDIR "lib") endif() @@ -59,6 +69,7 @@ set(TDUTILS_SOURCE td/utils/port/Stat.cpp td/utils/port/StdStreams.cpp td/utils/port/thread_local.cpp + td/utils/port/user.cpp td/utils/port/UdpSocketFd.cpp td/utils/port/uname.cpp td/utils/port/user.cpp @@ -95,7 +106,6 @@ set(TDUTILS_SOURCE td/utils/JsonBuilder.cpp td/utils/logging.cpp td/utils/misc.cpp - td/utils/MimeType.cpp td/utils/MpmcQueue.cpp td/utils/OptionParser.cpp td/utils/PathView.cpp @@ -105,9 +115,10 @@ set(TDUTILS_SOURCE td/utils/StackAllocator.cpp td/utils/Status.cpp td/utils/StringBuilder.cpp - td/utils/tests.cpp td/utils/Time.cpp td/utils/Timer.cpp + td/utils/TsFileLog.cpp + td/utils/tests.cpp td/utils/tl_parsers.cpp td/utils/translit.cpp td/utils/TsFileLog.cpp @@ -139,6 +150,7 @@ set(TDUTILS_SOURCE td/utils/port/StdStreams.h td/utils/port/thread.h td/utils/port/thread_local.h + td/utils/port/user.h td/utils/port/UdpSocketFd.h td/utils/port/uname.h td/utils/port/user.h @@ -205,7 +217,6 @@ set(TDUTILS_SOURCE td/utils/List.h td/utils/logging.h td/utils/MemoryLog.h - td/utils/MimeType.h td/utils/misc.h td/utils/MovableValue.h td/utils/MpmcQueue.h @@ -232,6 +243,7 @@ set(TDUTILS_SOURCE td/utils/SpinLock.h td/utils/StackAllocator.h td/utils/Status.h + td/utils/StealingQueue.h td/utils/Storer.h td/utils/StorerBase.h td/utils/StringBuilder.h @@ -241,6 +253,7 @@ set(TDUTILS_SOURCE td/utils/Time.h td/utils/TimedStat.h td/utils/Timer.h + td/utils/TsFileLog.h td/utils/tl_helpers.h td/utils/tl_parsers.h td/utils/tl_storers.h @@ -257,12 +270,19 @@ set(TDUTILS_SOURCE td/utils/VectorQueue.h ) +if (TDUTILS_MIME_TYPE) + set(TDUTILS_SOURCE + ${TDUTILS_SOURCE} + td/utils/MimeType.cpp + td/utils/MimeType.h + ) +endif() + set(TDUTILS_TEST_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/test/buffer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/ConcurrentHashMap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/crypto.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/Enumerator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test/EpochBasedMemoryReclamation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/filesystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/gzip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/HazardPointers.cpp @@ -280,6 +300,7 @@ set(TDUTILS_TEST_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/test/pq.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/SharedObjectPool.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/SharedSlice.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/StealingQueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/variant.cpp PARENT_SCOPE ) @@ -293,7 +314,7 @@ if (WIN32) # target_link_libraries(tdutils PRIVATE ${WS2_32_LIBRARY} ${MSWSOCK_LIBRARY}) target_link_libraries(tdutils PRIVATE ws2_32 Mswsock Normaliz psapi) endif() -if (NOT CMAKE_CROSSCOMPILING) +if (NOT CMAKE_CROSSCOMPILING AND TDUTILS_MIME_TYPE) add_dependencies(tdutils tdmime_auto) endif() @@ -316,7 +337,11 @@ if (CRC32C_FOUND) target_link_libraries(tdutils PRIVATE crc32c) endif() if (ABSL_FOUND) - target_link_libraries(tdutils PUBLIC absl::flat_hash_map absl::flat_hash_set absl::hash) + target_link_libraries_system(tdutils absl::flat_hash_map absl::flat_hash_set absl::hash) +endif() + +if (WIN32 AND WINGETOPT_FOUND) + target_link_libraries(tdutils PRIVATE wingetopt) endif() if (ANDROID) diff --git a/tdutils/generate/CMakeLists.txt b/tdutils/generate/CMakeLists.txt index 9eca574ad..07353e519 100644 --- a/tdutils/generate/CMakeLists.txt +++ b/tdutils/generate/CMakeLists.txt @@ -3,6 +3,10 @@ cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) # Generates files for MIME type <-> extension conversions # DEPENDS ON: gperf grep bash/powershell +if (NOT TDUTILS_MIME_TYPE) + return() +endif() + file(MAKE_DIRECTORY auto) set(TDMIME_SOURCE diff --git a/tdutils/td/utils/BigNum.cpp b/tdutils/td/utils/BigNum.cpp index 3d216466c..f83cd3268 100644 --- a/tdutils/td/utils/BigNum.cpp +++ b/tdutils/td/utils/BigNum.cpp @@ -88,7 +88,9 @@ BigNum BigNum::from_binary(Slice str) { } BigNum BigNum::from_le_binary(Slice str) { -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) +#if defined(OPENSSL_IS_BORINGSSL) + return BigNum(make_unique(BN_le2bn(str.ubegin(), narrow_cast(str.size()), nullptr))); +#elif OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) return BigNum(make_unique(BN_lebin2bn(str.ubegin(), narrow_cast(str.size()), nullptr))); #else string str_copy = str.str(); @@ -204,7 +206,7 @@ string BigNum::to_binary(int exact_size) const { } string BigNum::to_le_binary(int exact_size) const { -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) || defined(OPENSSL_IS_BORINGSSL) int num_size = get_num_bytes(); if (exact_size == -1) { exact_size = num_size; @@ -212,7 +214,11 @@ string BigNum::to_le_binary(int exact_size) const { CHECK(exact_size >= num_size); } string res(exact_size, '\0'); +#if defined(OPENSSL_IS_BORINGSSL) + BN_bn2le_padded(MutableSlice(res).ubegin(), exact_size, impl_->big_num); +#else BN_bn2lebinpad(impl_->big_num, MutableSlice(res).ubegin(), exact_size); +#endif return res; #else string result = to_binary(exact_size); diff --git a/tdutils/td/utils/BufferedFd.h b/tdutils/td/utils/BufferedFd.h index b2841e996..32ed3d7e6 100644 --- a/tdutils/td/utils/BufferedFd.h +++ b/tdutils/td/utils/BufferedFd.h @@ -72,6 +72,12 @@ class BufferedFd : public BufferedFdBase { ~BufferedFd(); void close(); + size_t left_unread() { + return input_reader_.size(); + } + size_t left_unwritten() { + return output_reader_.size(); + } Result flush_read(size_t max_read = std::numeric_limits::max()) TD_WARN_UNUSED_RESULT; Result flush_write() TD_WARN_UNUSED_RESULT; diff --git a/tdutils/td/utils/CancellationToken.h b/tdutils/td/utils/CancellationToken.h index 36e88eece..54277feb7 100644 --- a/tdutils/td/utils/CancellationToken.h +++ b/tdutils/td/utils/CancellationToken.h @@ -20,8 +20,13 @@ struct RawCancellationToken { class CancellationToken { public: explicit operator bool() const { + // Empty CancellationToken is never cancelled + if (!token_) { + return false; + } return token_->is_cancelled_.load(std::memory_order_acquire); } + CancellationToken() = default; explicit CancellationToken(std::shared_ptr token) : token_(std::move(token)) { } diff --git a/tdutils/td/utils/FileLog.cpp b/tdutils/td/utils/FileLog.cpp index aae750847..9f4639af6 100644 --- a/tdutils/td/utils/FileLog.cpp +++ b/tdutils/td/utils/FileLog.cpp @@ -82,7 +82,7 @@ void FileLog::append(CSlice cslice, int log_level) { process_fatal_error(cslice); } - if (size_ > rotate_threshold_) { + if (size_ > rotate_threshold_ || want_rotate_.load(std::memory_order_relaxed)) { auto status = rename(path_, PSLICE() << path_ << ".old"); if (status.is_error()) { process_fatal_error(PSLICE() << status.error() << " in " << __FILE__ << " at " << __LINE__); @@ -98,9 +98,13 @@ void FileLog::rotate() { do_rotate(); } +void FileLog::lazy_rotate() { + want_rotate_ = true; +} + void FileLog::do_rotate() { - auto current_verbosity_level = GET_VERBOSITY_LEVEL(); - SET_VERBOSITY_LEVEL(std::numeric_limits::min()); // to ensure that nothing will be printed to the closed log + want_rotate_ = false; + td::ScopedDisableLog disable_log; // to ensure that nothing will be printed to the closed log CHECK(!path_.empty()); fd_.close(); auto r_fd = FileFd::open(path_, FileFd::Create | FileFd::Truncate | FileFd::Write); @@ -112,7 +116,12 @@ void FileLog::do_rotate() { fd_.get_native_fd().duplicate(Stderr().get_native_fd()).ignore(); } size_ = 0; - SET_VERBOSITY_LEVEL(current_verbosity_level); +} + +Result> FileLog::create(string path, int64 rotate_threshold, bool redirect_stderr) { + auto l = make_unique(); + TRY_STATUS(l->init(std::move(path), rotate_threshold, redirect_stderr)); + return std::move(l); } } // namespace td diff --git a/tdutils/td/utils/FileLog.h b/tdutils/td/utils/FileLog.h index 0dd7b4fe5..6a0d78907 100644 --- a/tdutils/td/utils/FileLog.h +++ b/tdutils/td/utils/FileLog.h @@ -18,6 +18,8 @@ class FileLog : public LogInterface { static constexpr int64 DEFAULT_ROTATE_THRESHOLD = 10 * (1 << 20); public: + static Result> create(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD, + bool redirect_stderr = true); Status init(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD, bool redirect_stderr = true); Slice get_path() const; @@ -32,12 +34,15 @@ class FileLog : public LogInterface { void rotate() override; + void lazy_rotate(); + private: FileFd fd_; string path_; int64 size_ = 0; int64 rotate_threshold_ = 0; bool redirect_stderr_ = false; + std::atomic want_rotate_{}; void do_rotate(); }; diff --git a/tdutils/td/utils/Heap.h b/tdutils/td/utils/Heap.h index b4449c303..19f44ad75 100644 --- a/tdutils/td/utils/Heap.h +++ b/tdutils/td/utils/Heap.h @@ -37,6 +37,10 @@ class KHeap { return array_[0].key_; } + HeapNode *top() const { + return array_[0].node_; + } + HeapNode *pop() { CHECK(!empty()); HeapNode *result = array_[0].node_; diff --git a/tdutils/td/utils/JsonBuilder.h b/tdutils/td/utils/JsonBuilder.h index 012b31b2f..963dad99c 100644 --- a/tdutils/td/utils/JsonBuilder.h +++ b/tdutils/td/utils/JsonBuilder.h @@ -172,7 +172,7 @@ class JsonObjectScope; class JsonBuilder { public: - explicit JsonBuilder(StringBuilder &&sb, int32 offset = -1) : sb_(std::move(sb)), offset_(offset) { + explicit JsonBuilder(StringBuilder &&sb = {}, int32 offset = -1) : sb_(std::move(sb)), offset_(offset) { } StringBuilder &string_builder() { return sb_; @@ -350,7 +350,10 @@ class JsonArrayScope : public JsonScope { } void leave() { jb_->dec_offset(); - jb_->print_offset(); + if (jb_->is_pretty()) { + *sb_ << "\n"; + jb_->print_offset(); + } *sb_ << "]"; } template @@ -369,7 +372,10 @@ class JsonArrayScope : public JsonScope { } else { is_first_ = true; } - jb_->print_offset(); + if (jb_->is_pretty()) { + *sb_ << "\n"; + jb_->print_offset(); + } return jb_->enter_value(); } @@ -391,7 +397,10 @@ class JsonObjectScope : public JsonScope { } void leave() { jb_->dec_offset(); - jb_->print_offset(); + if (jb_->is_pretty()) { + *sb_ << "\n"; + jb_->print_offset(); + } *sb_ << "}"; } template @@ -402,7 +411,10 @@ class JsonObjectScope : public JsonScope { } else { is_first_ = true; } - jb_->print_offset(); + if (jb_->is_pretty()) { + *sb_ << "\n"; + jb_->print_offset(); + } jb_->enter_value() << key; if (jb_->is_pretty()) { *sb_ << " : "; diff --git a/tdutils/td/utils/MpmcQueue.h b/tdutils/td/utils/MpmcQueue.h index ef16ba255..4a2fc9e68 100644 --- a/tdutils/td/utils/MpmcQueue.h +++ b/tdutils/td/utils/MpmcQueue.h @@ -105,24 +105,28 @@ template class OneValue { public: bool set_value(T *value) { - T *was = nullptr; + T *was = Empty(); return state_.compare_exchange_strong(was, value, std::memory_order_acq_rel); } bool get_value(T *&value) { value = state_.exchange(Taken(), std::memory_order_acq_rel); - return value != nullptr; + return value != Empty(); } void reset() { - state_ = nullptr; + state_ = Empty(); } OneValue() { } private: - std::atomic state_{nullptr}; - T *Taken() { - static T xxx; - return &xxx; + std::atomic state_{Empty()}; + static T *Empty() { + static int64 xxx; + return reinterpret_cast(&xxx); + } + static T *Taken() { + static int64 xxx; + return reinterpret_cast(&xxx); } }; diff --git a/tdutils/td/utils/MpmcWaiter.h b/tdutils/td/utils/MpmcWaiter.h index 0c3a62d9b..df1e70120 100644 --- a/tdutils/td/utils/MpmcWaiter.h +++ b/tdutils/td/utils/MpmcWaiter.h @@ -7,58 +7,81 @@ #pragma once #include "td/utils/common.h" +#include "td/utils/logging.h" #include "td/utils/port/thread.h" #include +#include #include #include namespace td { -class MpmcWaiter { +class MpmcEagerWaiter { public: - int wait(int yields, uint32 worker_id) { - if (yields < RoundsTillSleepy) { + struct Slot { + private: + friend class MpmcEagerWaiter; + int yields; + uint32 worker_id; + }; + void init_slot(Slot &slot, uint32 worker_id) { + slot.yields = 0; + slot.worker_id = worker_id; + } + void wait(Slot &slot) { + if (slot.yields < RoundsTillSleepy) { td::this_thread::yield(); - return yields + 1; - } else if (yields == RoundsTillSleepy) { + slot.yields++; + return; + } else if (slot.yields == RoundsTillSleepy) { auto state = state_.load(std::memory_order_relaxed); if (!State::has_worker(state)) { - auto new_state = State::with_worker(state, worker_id); + auto new_state = State::with_worker(state, slot.worker_id); if (state_.compare_exchange_strong(state, new_state, std::memory_order_acq_rel)) { td::this_thread::yield(); - return yields + 1; + slot.yields++; + return; } if (state == State::awake()) { - return 0; + slot.yields = 0; + return; } } td::this_thread::yield(); - return 0; - } else if (yields < RoundsTillAsleep) { + slot.yields = 0; + return; + } else if (slot.yields < RoundsTillAsleep) { auto state = state_.load(std::memory_order_acquire); - if (State::still_sleepy(state, worker_id)) { + if (State::still_sleepy(state, slot.worker_id)) { td::this_thread::yield(); - return yields + 1; + slot.yields++; + return; } - return 0; + slot.yields = 0; + return; } else { auto state = state_.load(std::memory_order_acquire); - if (State::still_sleepy(state, worker_id)) { + if (State::still_sleepy(state, slot.worker_id)) { std::unique_lock lock(mutex_); if (state_.compare_exchange_strong(state, State::asleep(), std::memory_order_acq_rel)) { condition_variable_.wait(lock); } } - return 0; + slot.yields = 0; + return; } } - int stop_wait(int yields, uint32 worker_id) { - if (yields > RoundsTillSleepy) { + void stop_wait(Slot &slot) { + if (slot.yields > RoundsTillSleepy) { notify_cold(); } - return 0; + slot.yields = 0; + return; + } + + void close() { } void notify() { @@ -90,8 +113,8 @@ class MpmcWaiter { return (state >> 1) == (worker + 1); } }; - //enum { RoundsTillSleepy = 32, RoundsTillAsleep = 64 }; - enum { RoundsTillSleepy = 1, RoundsTillAsleep = 2 }; + enum { RoundsTillSleepy = 32, RoundsTillAsleep = 64 }; + // enum { RoundsTillSleepy = 1, RoundsTillAsleep = 2 }; std::atomic state_{State::awake()}; std::mutex mutex_; std::condition_variable condition_variable_; @@ -105,4 +128,208 @@ class MpmcWaiter { } }; +class MpmcSleepyWaiter { + public: + struct Slot { + private: + friend class MpmcSleepyWaiter; + + enum State { Search, Work, Sleep } state_{Work}; + + void park() { + std::unique_lock guard(mutex_); + condition_variable_.wait(guard, [&] { return unpark_flag_; }); + unpark_flag_ = false; + } + + bool cancel_park() { + auto res = unpark_flag_; + unpark_flag_ = false; + return res; + } + + void unpark() { + //TODO: try unlock guard before notify_all + std::unique_lock guard(mutex_); + unpark_flag_ = true; + condition_variable_.notify_all(); + } + + std::mutex mutex_; + std::condition_variable condition_variable_; + bool unpark_flag_{false}; // TODO: move out of lock + int yield_cnt{0}; + int32 worker_id{0}; + char padding[TD_CONCURRENCY_PAD]; + }; + + // There are a lot of workers + // Each has a slot + // + // States of a worker: + // - searching for work | Search + // - processing work | Work + // - sleeping | Sleep + // + // When somebody adds a work it calls notify + // + // notify + // if there are workers in search phase do nothing. + // if all workers are awake do nothing + // otherwise wake some random worker + // + // Initially all workers are in Search mode. + // + // When worker found nothing it may try to call wait. + // This may put it in a Sleep for some time. + // After wait return worker will be in Search state again. + // + // Suppose worker found a work and ready to process it. + // Than it may call stop_wait. This will cause transition from + // Search to Work state. + // + // Main invariant: + // After notify is called there should be at least on worker in Search or Work state. + // If possible - in Search state + // + + void init_slot(Slot &slot, int32 worker_id) { + slot.state_ = Slot::State::Work; + slot.unpark_flag_ = false; + slot.worker_id = worker_id; + VLOG(waiter) << "Init slot " << worker_id; + } + + int VERBOSITY_NAME(waiter) = VERBOSITY_NAME(DEBUG) + 10; + void wait(Slot &slot) { + if (slot.state_ == Slot::State::Work) { + VLOG(waiter) << "Work -> Search"; + state_++; + slot.state_ = Slot::State::Search; + slot.yield_cnt = 0; + return; + } + if (slot.state_ == Slot::Search) { + if (slot.yield_cnt++ < 10 && false) { + td::this_thread::yield(); + return; + } + + slot.state_ = Slot::State::Sleep; + std::unique_lock guard(sleepers_mutex_); + auto state_view = StateView(state_.fetch_add((1 << PARKING_SHIFT) - 1)); + CHECK(state_view.searching_count != 0); + bool should_search = state_view.searching_count == 1; + if (closed_) { + return; + } + sleepers_.push_back(&slot); + LOG_CHECK(slot.unpark_flag_ == false) << slot.worker_id; + VLOG(waiter) << "add to sleepers " << slot.worker_id; + //guard.unlock(); + if (should_search) { + VLOG(waiter) << "Search -> Search once then Sleep "; + return; + } + VLOG(waiter) << "Search -> Sleep " << state_view.searching_count << " " << state_view.parked_count; + } + + CHECK(slot.state_ == Slot::State::Sleep); + VLOG(waiter) << "Park " << slot.worker_id; + slot.park(); + VLOG(waiter) << "Resume " << slot.worker_id; + slot.state_ = Slot::State::Search; + slot.yield_cnt = 0; + } + + void stop_wait(Slot &slot) { + if (slot.state_ == Slot::State::Work) { + return; + } + if (slot.state_ == Slot::State::Sleep) { + VLOG(waiter) << "Search once then Sleep -> Work/Search " << slot.worker_id; + slot.state_ = Slot::State::Work; + std::unique_lock guard(sleepers_mutex_); + auto it = std::find(sleepers_.begin(), sleepers_.end(), &slot); + if (it != sleepers_.end()) { + sleepers_.erase(it); + VLOG(waiter) << "remove from sleepers " << slot.worker_id; + state_.fetch_sub((1 << PARKING_SHIFT) - 1); + guard.unlock(); + } else { + guard.unlock(); + VLOG(waiter) << "not in sleepers" << slot.worker_id; + CHECK(slot.cancel_park()); + } + } + VLOG(waiter) << "Search once then Sleep -> Work " << slot.worker_id; + slot.state_ = Slot::State::Search; + auto state_view = StateView(state_.fetch_sub(1)); + CHECK(state_view.searching_count != 0); + CHECK(state_view.searching_count < 1000); + bool should_notify = state_view.searching_count == 1; + if (should_notify) { + VLOG(waiter) << "Notify others"; + notify(); + } + VLOG(waiter) << "Search -> Work "; + slot.state_ = Slot::State::Work; + } + + void notify() { + auto view = StateView(state_.load()); + //LOG(ERROR) << view.parked_count; + if (view.searching_count > 0 || view.parked_count == 0) { + VLOG(waiter) << "Ingore notify: " << view.searching_count << " " << view.parked_count; + return; + } + + VLOG(waiter) << "Notify: " << view.searching_count << " " << view.parked_count; + std::unique_lock guard(sleepers_mutex_); + + view = StateView(state_.load()); + if (view.searching_count > 0) { + VLOG(waiter) << "Skip notify: got searching"; + return; + } + + CHECK(view.parked_count == static_cast(sleepers_.size())); + if (sleepers_.empty()) { + VLOG(waiter) << "Skip notify: no sleepers"; + return; + } + + auto sleeper = sleepers_.back(); + sleepers_.pop_back(); + state_.fetch_sub((1 << PARKING_SHIFT) - 1); + VLOG(waiter) << "Unpark " << sleeper->worker_id; + sleeper->unpark(); + } + + void close() { + StateView state(state_.load()); + LOG_CHECK(state.parked_count == 0) << state.parked_count; + LOG_CHECK(state.searching_count == 0) << state.searching_count; + } + + private: + static constexpr td::int32 PARKING_SHIFT = 16; + struct StateView { + td::int32 parked_count; + td::int32 searching_count; + explicit StateView(int32 x) { + parked_count = x >> PARKING_SHIFT; + searching_count = x & ((1 << PARKING_SHIFT) - 1); + } + }; + std::atomic state_{0}; + + std::mutex sleepers_mutex_; + std::vector sleepers_; + + bool closed_ = false; +}; + +using MpmcWaiter = MpmcSleepyWaiter; + } // namespace td diff --git a/tdutils/td/utils/OptionParser.cpp b/tdutils/td/utils/OptionParser.cpp index 5816a4b2a..ec1f1aaaf 100644 --- a/tdutils/td/utils/OptionParser.cpp +++ b/tdutils/td/utils/OptionParser.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/OptionParser.h" #include "td/utils/misc.h" @@ -19,6 +20,11 @@ void OptionParser::set_description(string description) { void OptionParser::add_option(Option::Type type, char short_key, Slice long_key, Slice description, std::function callback) { + for (auto &option : options_) { + if (option.short_key == short_key || (!long_key.empty() && long_key == option.long_key)) { + LOG(ERROR) << "Ignore duplicated option '" << short_key << "' '" << long_key << "'"; + } + } options_.push_back(Option{type, short_key, long_key.str(), description.str(), std::move(callback)}); } diff --git a/tdutils/td/utils/OptionParser.h b/tdutils/td/utils/OptionParser.h index fa65f7136..2fbd86698 100644 --- a/tdutils/td/utils/OptionParser.h +++ b/tdutils/td/utils/OptionParser.h @@ -36,6 +36,10 @@ class OptionParser { void add_checked_option(char short_key, Slice long_key, Slice description, std::function callback); + void add_option(char short_key, Slice long_key, Slice description, std::function callback) = delete; + + void add_option(char short_key, Slice long_key, Slice description, std::function callback) = delete; + void add_option(char short_key, Slice long_key, Slice description, std::function callback); void add_option(char short_key, Slice long_key, Slice description, std::function callback); diff --git a/tdutils/td/utils/PathView.cpp b/tdutils/td/utils/PathView.cpp index 4487f867e..867372051 100644 --- a/tdutils/td/utils/PathView.cpp +++ b/tdutils/td/utils/PathView.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/PathView.h" #include "td/utils/misc.h" @@ -36,4 +37,22 @@ Slice PathView::relative(Slice path, Slice dir, bool force) { return path; } +Slice PathView::dir_and_file(Slice path) { + auto last_slash = static_cast(path.size()) - 1; + while (last_slash >= 0 && !is_slash(path[last_slash])) { + last_slash--; + } + if (last_slash < 0) { + return Slice(); + } + last_slash--; + while (last_slash >= 0 && !is_slash(path[last_slash])) { + last_slash--; + } + if (last_slash < 0) { + return Slice(); + } + return path.substr(last_slash + 1); +} + } // namespace td diff --git a/tdutils/td/utils/PathView.h b/tdutils/td/utils/PathView.h index 072411413..52f5e6788 100644 --- a/tdutils/td/utils/PathView.h +++ b/tdutils/td/utils/PathView.h @@ -28,6 +28,9 @@ class PathView { Slice parent_dir() const { return path_.substr(0, last_slash_ + 1); } + Slice parent_dir_noslash() const { + return last_slash_ <= 0 ? td::Slice(".") : path_.substr(0, last_slash_); + } Slice extension() const { if (last_dot_ == static_cast(path_.size())) { @@ -61,6 +64,7 @@ class PathView { } static Slice relative(Slice path, Slice dir, bool force = false); + static Slice dir_and_file(Slice path); private: static bool is_slash(char c) { diff --git a/tdutils/td/utils/Random.cpp b/tdutils/td/utils/Random.cpp index 73e1990ec..45fb2c2c5 100644 --- a/tdutils/td/utils/Random.cpp +++ b/tdutils/td/utils/Random.cpp @@ -39,6 +39,11 @@ void Random::secure_bytes(unsigned char *ptr, size_t size) { buf_pos = BUF_SIZE; generation = 0; } + if (ptr == nullptr) { + td::MutableSlice(buf, BUF_SIZE).fill_zero_secure(); + buf_pos = BUF_SIZE; + return; + } if (generation != random_seed_generation.load(std::memory_order_relaxed)) { generation = random_seed_generation.load(std::memory_order_acquire); buf_pos = BUF_SIZE; @@ -97,6 +102,10 @@ void Random::add_seed(Slice bytes, double entropy) { RAND_add(bytes.data(), static_cast(bytes.size()), entropy); random_seed_generation++; } + +void Random::secure_cleanup() { + Random::secure_bytes(nullptr, 0); +} #endif static unsigned int rand_device_helper() { @@ -134,13 +143,21 @@ int Random::fast(int min, int max) { return static_cast(min + fast_uint32() % (max - min + 1)); // TODO signed_cast } +double Random::fast(double min, double max) { + DCHECK(min <= max); + return min + + fast_uint32() * 1.0 / + (static_cast(std::numeric_limits::max()) - std::numeric_limits::min()) * + (max - min); +} + Random::Xorshift128plus::Xorshift128plus(uint64 seed) { auto next = [&] { // splitmix64 - seed += static_cast(0x9E3779B97F4A7C15); + seed += static_cast(0x9E3779B97F4A7C15ull); uint64 z = seed; - z = (z ^ (z >> 30)) * static_cast(0xBF58476D1CE4E5B9); - z = (z ^ (z >> 27)) * static_cast(0x94D049BB133111EB); + z = (z ^ (z >> 30)) * static_cast(0xBF58476D1CE4E5B9ull); + z = (z ^ (z >> 27)) * static_cast(0x94D049BB133111EBull); return z ^ (z >> 31); }; seed_[0] = next(); @@ -164,6 +181,9 @@ uint64 Random::Xorshift128plus::operator()() { int Random::Xorshift128plus::fast(int min, int max) { return static_cast((*this)() % (max - min + 1) + min); } +int64 Random::Xorshift128plus::fast64(int64 min, int64 max) { + return static_cast((*this)() % (max - min + 1) + min); +} void Random::Xorshift128plus::bytes(MutableSlice dest) { int cnt = 0; diff --git a/tdutils/td/utils/Random.h b/tdutils/td/utils/Random.h index 3cb932f57..d7e604b17 100644 --- a/tdutils/td/utils/Random.h +++ b/tdutils/td/utils/Random.h @@ -8,6 +8,7 @@ #include "td/utils/common.h" #include "td/utils/Slice.h" +#include "td/utils/Span.h" namespace td { @@ -23,6 +24,7 @@ class Random { // works only for current thread static void add_seed(Slice bytes, double entropy = 0); + static void secure_cleanup(); #endif static uint32 fast_uint32(); @@ -30,13 +32,21 @@ class Random { // distribution is not uniform, min and max are included static int fast(int min, int max); + static double fast(double min, double max); + class Fast { + public: + uint64 operator()() { + return fast_uint64(); + } + }; class Xorshift128plus { public: explicit Xorshift128plus(uint64 seed); Xorshift128plus(uint64 seed_a, uint64 seed_b); uint64 operator()(); int fast(int min, int max); + int64 fast64(int64 min, int64 max); void bytes(MutableSlice dest); private: @@ -44,4 +54,12 @@ class Random { }; }; +template +void random_shuffle(td::MutableSpan v, R &rnd) { + for (std::size_t i = 1; i < v.size(); i++) { + auto pos = static_cast(rnd() % (i + 1)); + std::swap(v[i], v[pos]); + } +} + } // namespace td diff --git a/tdutils/td/utils/SharedObjectPool.h b/tdutils/td/utils/SharedObjectPool.h index 8dd9e24a4..b054cbcb2 100644 --- a/tdutils/td/utils/SharedObjectPool.h +++ b/tdutils/td/utils/SharedObjectPool.h @@ -37,9 +37,7 @@ class AtomicRefCnt { }; template -class SharedPtrRaw - : public DeleterT - , private MpscLinkQueueImpl::Node { +class SharedPtrRaw : public DeleterT, private MpscLinkQueueImpl::Node { public: explicit SharedPtrRaw(DeleterT deleter) : DeleterT(std::move(deleter)), ref_cnt_{0}, option_magic_(Magic) { } @@ -88,6 +86,7 @@ template > class SharedPtr { public: using Raw = detail::SharedPtrRaw; + struct acquire_t {}; SharedPtr() = default; ~SharedPtr() { if (!raw_) { @@ -100,6 +99,8 @@ class SharedPtr { raw_->inc(); } } + SharedPtr(acquire_t, Raw *raw) : raw_(raw) { + } SharedPtr(const SharedPtr &other) : SharedPtr(other.raw_) { } SharedPtr &operator=(const SharedPtr &other) { diff --git a/tdutils/td/utils/SharedSlice.cpp b/tdutils/td/utils/SharedSlice.cpp index 02b682a53..9f5cb6249 100644 --- a/tdutils/td/utils/SharedSlice.cpp +++ b/tdutils/td/utils/SharedSlice.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/SharedSlice.h" #include "td/utils/buffer.h" diff --git a/tdutils/td/utils/SharedSlice.h b/tdutils/td/utils/SharedSlice.h index d4c201623..fe69405f0 100644 --- a/tdutils/td/utils/SharedSlice.h +++ b/tdutils/td/utils/SharedSlice.h @@ -4,9 +4,9 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #pragma once -#include "td/utils/common.h" #include "td/utils/Slice.h" #include @@ -72,6 +72,9 @@ class UnsafeSharedSlice { public: UnsafeSharedSlice() = default; UnsafeSharedSlice clone() const { + if (is_null()) { + return UnsafeSharedSlice(); + } header()->inc(); return UnsafeSharedSlice(ptr_.get()); } diff --git a/tdutils/td/utils/Slice.cpp b/tdutils/td/utils/Slice.cpp index e3ae1acee..285870a9f 100644 --- a/tdutils/td/utils/Slice.cpp +++ b/tdutils/td/utils/Slice.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/Slice.h" #if TD_HAVE_OPENSSL diff --git a/tdutils/td/utils/Span.h b/tdutils/td/utils/Span.h index da265dad5..7e93a9e64 100644 --- a/tdutils/td/utils/Span.h +++ b/tdutils/td/utils/Span.h @@ -85,6 +85,16 @@ class SpanImpl { return data_[size() - 1]; } + InnerT &front() { + DCHECK(!empty()); + return data_[0]; + } + + const InnerT &front() const { + DCHECK(!empty()); + return data_[0]; + } + InnerT *data() const { return data_; } @@ -95,7 +105,6 @@ class SpanImpl { InnerT *end() const { return data_ + size_; } - std::reverse_iterator rbegin() const { return std::reverse_iterator(end()); } @@ -111,8 +120,9 @@ class SpanImpl { } SpanImpl &truncate(size_t size) { - CHECK(size <= size_); - size_ = size; + if (size < size_) { + size_ = size; + } return *this; } @@ -138,9 +148,45 @@ template Span span(const T *ptr, size_t size) { return Span(ptr, size); } +template +Span span(const std::vector &vec) { + return Span(vec); +} + template MutableSpan mutable_span(T *ptr, size_t size) { return MutableSpan(ptr, size); } +template +MutableSpan mutable_span(std::vector &vec) { + return MutableSpan(vec); +} + +template +Span span_one(const T &value) { + return td::Span(&value, 1); +} +template +MutableSpan mutable_span_one(T &value) { + return td::MutableSpan(&value, 1); +} + +template +Span as_span(Span span) { + return span; +} +template +Span as_span(const std::vector &vec) { + return Span(vec); +} + +template +MutableSpan as_mutable_span(MutableSpan span) { + return span; +} +template +MutableSpan as_mutable_span(std::vector &vec) { + return MutableSpan(vec); +} } // namespace td diff --git a/tdutils/td/utils/Status.h b/tdutils/td/utils/Status.h index 298a9c883..0023eb6ef 100644 --- a/tdutils/td/utils/Status.h +++ b/tdutils/td/utils/Status.h @@ -36,16 +36,46 @@ } \ } +#define TRY_STATUS_PROMISE(promise_name, status) \ + { \ + auto try_status = (status); \ + if (try_status.is_error()) { \ + promise_name.set_error(std::move(try_status)); \ + return; \ + } \ + } + +#define TRY_STATUS_PROMISE_PREFIX(promise_name, status, prefix) \ + { \ + auto try_status = (status); \ + if (try_status.is_error()) { \ + promise_name.set_error(try_status.move_as_error_prefix(prefix)); \ + return; \ + } \ + } + #define TRY_RESULT(name, result) TRY_RESULT_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result) +#define TRY_RESULT_PROMISE(promise_name, name, result) \ + TRY_RESULT_PROMISE_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result) + #define TRY_RESULT_ASSIGN(name, result) TRY_RESULT_IMPL(TD_CONCAT(r_response, __LINE__), name, result) +#define TRY_RESULT_PROMISE_ASSIGN(promise_name, name, result) \ + TRY_RESULT_PROMISE_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result) + #define TRY_RESULT_PREFIX(name, result, prefix) \ TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix) #define TRY_RESULT_PREFIX_ASSIGN(name, result, prefix) \ TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result, prefix) +#define TRY_RESULT_PROMISE_PREFIX(promise_name, name, result, prefix) \ + TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix) + +#define TRY_RESULT_PROMISE_PREFIX_ASSIGN(promise_name, name, result, prefix) \ + TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result, prefix) + #define TRY_RESULT_IMPL(r_name, name, result) \ auto r_name = (result); \ if (r_name.is_error()) { \ @@ -60,6 +90,22 @@ } \ name = r_name.move_as_ok(); +#define TRY_RESULT_PROMISE_IMPL(promise_name, r_name, name, result) \ + auto r_name = (result); \ + if (r_name.is_error()) { \ + promise_name.set_error(r_name.move_as_error()); \ + return; \ + } \ + name = r_name.move_as_ok(); + +#define TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, r_name, name, result, prefix) \ + auto r_name = (result); \ + if (r_name.is_error()) { \ + promise_name.set_error(r_name.move_as_error_prefix(prefix)); \ + return; \ + } \ + name = r_name.move_as_ok(); + #define LOG_STATUS(status) \ { \ auto log_status = (status); \ @@ -272,7 +318,16 @@ class Status { return std::move(*this); } - Status move_as_error_prefix(Slice prefix) TD_WARN_UNUSED_RESULT { + Auto move_as_ok() { + UNREACHABLE(); + return {}; + } + + Status move_as_error_prefix(const Status &status) const TD_WARN_UNUSED_RESULT { + return status.move_as_error_suffix(message()); + } + + Status move_as_error_prefix(Slice prefix) const TD_WARN_UNUSED_RESULT { CHECK(is_error()); Info info = get_info(); switch (info.error_type) { @@ -285,6 +340,19 @@ class Status { return {}; } } + Status move_as_error_suffix(Slice suffix) const TD_WARN_UNUSED_RESULT { + CHECK(is_error()); + Info info = get_info(); + switch (info.error_type) { + case ErrorType::General: + return Error(code(), PSLICE() << message() << suffix); + case ErrorType::Os: + return Status(false, ErrorType::Os, code(), PSLICE() << message() << suffix); + default: + UNREACHABLE(); + return {}; + } + } private: struct Info { @@ -366,6 +434,7 @@ class Status { template class Result { public: + using ValueT = T; Result() : status_(Status::Error<-1>()) { } template , Result>::value, int> = 0> @@ -462,6 +531,18 @@ class Result { }; return status_.move_as_error_prefix(prefix); } + Status move_as_error_prefix(const Status &prefix) TD_WARN_UNUSED_RESULT { + SCOPE_EXIT { + status_ = Status::Error<-5>(); + }; + return status_.move_as_error_prefix(prefix); + } + Status move_as_error_suffix(Slice suffix) TD_WARN_UNUSED_RESULT { + SCOPE_EXIT { + status_ = Status::Error<-5>(); + }; + return status_.move_as_error_suffix(suffix); + } const T &ok() const { LOG_CHECK(status_.is_ok()) << status_; return value_; @@ -489,6 +570,22 @@ class Result { *this = Result(); } + template + td::Result()(std::declval()))> move_map(F &&f) { + if (is_error()) { + return move_as_error(); + } + return f(move_as_ok()); + } + + template + decltype(std::declval()(std::declval())) move_fmap(F &&f) { + if (is_error()) { + return move_as_error(); + } + return f(move_as_ok()); + } + private: Status status_; union { diff --git a/tdutils/td/utils/StringBuilder.h b/tdutils/td/utils/StringBuilder.h index c8109d978..5491f8053 100644 --- a/tdutils/td/utils/StringBuilder.h +++ b/tdutils/td/utils/StringBuilder.h @@ -19,6 +19,8 @@ namespace td { class StringBuilder { public: explicit StringBuilder(MutableSlice slice, bool use_buffer = false); + StringBuilder() : StringBuilder({}, true) { + } void clear() { current_ptr_ = begin_ptr_; diff --git a/tdutils/td/utils/ThreadLocalStorage.h b/tdutils/td/utils/ThreadLocalStorage.h index 79dc82ede..4bbe8614d 100644 --- a/tdutils/td/utils/ThreadLocalStorage.h +++ b/tdutils/td/utils/ThreadLocalStorage.h @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #pragma once #include "td/utils/common.h" @@ -13,7 +14,6 @@ #include namespace td { - template class ThreadLocalStorage { public: @@ -51,5 +51,4 @@ class ThreadLocalStorage { return nodes_[thread_id]; } }; - } // namespace td diff --git a/tdutils/td/utils/ThreadSafeCounter.h b/tdutils/td/utils/ThreadSafeCounter.h index bfc667140..0b6cb114f 100644 --- a/tdutils/td/utils/ThreadSafeCounter.h +++ b/tdutils/td/utils/ThreadSafeCounter.h @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #pragma once #include "td/utils/common.h" @@ -16,7 +17,6 @@ #include namespace td { - template class ThreadSafeMultiCounter { public: @@ -31,6 +31,13 @@ class ThreadSafeMultiCounter { tls_.for_each([&](auto &value) { res += value[index].load(std::memory_order_relaxed); }); return res; } + void clear() { + tls_.for_each([&](auto &value) { + for (auto &x : value) { + x = 0; + } + }); + } private: ThreadLocalStorage, N>> tls_; @@ -80,7 +87,7 @@ class NamedThreadSafeCounter { } } CHECK(names_.size() < N); - names_.emplase_back(name.begin(), name.size()); + names_.emplace_back(name.begin(), name.size()); return get_counter_ref(names_.size() - 1); } @@ -101,6 +108,11 @@ class NamedThreadSafeCounter { } } + void clear() { + std::unique_lock guard(mutex_); + counter_.clear(); + } + friend StringBuilder &operator<<(StringBuilder &sb, const NamedThreadSafeCounter &counter) { counter.for_each([&sb](Slice name, int64 cnt) { sb << name << ": " << cnt << "\n"; }); return sb; diff --git a/tdutils/td/utils/Time.cpp b/tdutils/td/utils/Time.cpp index 0180c31d9..57dbc9ec8 100644 --- a/tdutils/td/utils/Time.cpp +++ b/tdutils/td/utils/Time.cpp @@ -7,14 +7,36 @@ #include "td/utils/Time.h" #include +#include namespace td { bool operator==(Timestamp a, Timestamp b) { return std::abs(a.at() - b.at()) < 1e-6; } +namespace { +std::atomic time_diff; +} double Time::now() { + return now_unadjusted() + time_diff.load(std::memory_order_relaxed); +} + +double Time::now_unadjusted() { return Clocks::monotonic(); } +void Time::jump_in_future(double at) { + auto old_time_diff = time_diff.load(); + + while (true) { + auto diff = at - now(); + if (diff < 0) { + return; + } + if (time_diff.compare_exchange_strong(old_time_diff, old_time_diff + diff)) { + return; + } + } +} + } // namespace td diff --git a/tdutils/td/utils/Time.h b/tdutils/td/utils/Time.h index 4652efd47..296720518 100644 --- a/tdutils/td/utils/Time.h +++ b/tdutils/td/utils/Time.h @@ -28,6 +28,10 @@ class Time { // As an alternative we may say that now_cached is a thread local copy of now return now(); } + static double now_unadjusted(); + + // Used for testing. After jump_in_future(at) is called, now() >= at. + static void jump_in_future(double at); }; inline void relax_timeout_at(double *timeout, double new_timeout) { @@ -58,12 +62,15 @@ class Timestamp { return Timestamp{timeout - Clocks::system() + Time::now()}; } - static Timestamp in(double timeout) { - return Timestamp{Time::now_cached() + timeout}; + static Timestamp in(double timeout, td::Timestamp now = td::Timestamp::now_cached()) { + return Timestamp{now.at() + timeout}; } + bool is_in_past(td::Timestamp now) const { + return at_ <= now.at(); + } bool is_in_past() const { - return at_ <= Time::now_cached(); + return is_in_past(now_cached()); } explicit operator bool() const { @@ -99,6 +106,10 @@ class Timestamp { } }; +inline bool operator<(const Timestamp &a, const Timestamp &b) { + return a.at() < b.at(); +} + template void store(const Timestamp ×tamp, StorerT &storer) { storer.store_binary(timestamp.at() - Time::now() + Clocks::system()); diff --git a/tdutils/td/utils/TimedStat.h b/tdutils/td/utils/TimedStat.h index 903192754..bbd4f16b2 100644 --- a/tdutils/td/utils/TimedStat.h +++ b/tdutils/td/utils/TimedStat.h @@ -7,6 +7,7 @@ #pragma once #include "td/utils/common.h" +#include "td/utils/optional.h" #include @@ -68,4 +69,27 @@ class TimedStat { } }; +template +struct MinMaxStat { + public: + using Event = T; + void on_event(Event event) { + if (!best_ || Cmp()(event, best_.value())) { + best_ = event; + } + } + td::optional get_stat() const { + return best_.copy(); + } + + private: + td::optional best_; +}; + +template +using MinStat = MinMaxStat>; + +template +using MaxStat = MinMaxStat>; + } // namespace td diff --git a/tdutils/td/utils/Timer.cpp b/tdutils/td/utils/Timer.cpp index c73b95420..318a8a584 100644 --- a/tdutils/td/utils/Timer.cpp +++ b/tdutils/td/utils/Timer.cpp @@ -12,15 +12,40 @@ namespace td { -Timer::Timer() : start_time_(Time::now()) { +Timer::Timer(bool is_paused) : is_paused_(is_paused) { + if (is_paused_) { + start_time_ = 0; + } else { + start_time_ = Time::now(); + } +} + +void Timer::pause() { + if (is_paused_) { + return; + } + elapsed_ += Time::now() - start_time_; + is_paused_ = true; +} + +void Timer::resume() { + if (!is_paused_) { + return; + } + start_time_ = Time::now(); + is_paused_ = false; } double Timer::elapsed() const { - return Time::now() - start_time_; + double res = elapsed_; + if (!is_paused_) { + res += Time::now() - start_time_; + } + return res; } StringBuilder &operator<<(StringBuilder &string_builder, const Timer &timer) { - return string_builder << "in " << Time::now() - timer.start_time_; + return string_builder << format::as_time(timer.elapsed()); } PerfWarningTimer::PerfWarningTimer(string name, double max_duration) diff --git a/tdutils/td/utils/Timer.h b/tdutils/td/utils/Timer.h index ac32cb09e..aa60890e5 100644 --- a/tdutils/td/utils/Timer.h +++ b/tdutils/td/utils/Timer.h @@ -12,14 +12,22 @@ namespace td { class Timer { public: - Timer(); + Timer() : Timer(false) { + } + explicit Timer(bool is_paused); + Timer(const Timer &other) = default; + Timer &operator=(const Timer &other) = default; double elapsed() const; + void pause(); + void resume(); private: friend StringBuilder &operator<<(StringBuilder &string_builder, const Timer &timer); + double elapsed_{0}; double start_time_; + bool is_paused_{false}; }; class PerfWarningTimer { diff --git a/tdutils/td/utils/TsFileLog.cpp b/tdutils/td/utils/TsFileLog.cpp index d1830068c..1d98d1b14 100644 --- a/tdutils/td/utils/TsFileLog.cpp +++ b/tdutils/td/utils/TsFileLog.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/TsFileLog.h" #include "td/utils/common.h" @@ -16,14 +17,14 @@ #include namespace td { - namespace detail { - -class TsFileLogImpl : public LogInterface { +class TsFileLog : public LogInterface { public: - Status init(string path) { + Status init(string path, td::int64 rotate_threshold, bool redirect_stderr) { path_ = std::move(path); - for (int32 i = 0; i < static_cast(logs_.size()); i++) { + rotate_threshold_ = rotate_threshold; + redirect_stderr_ = redirect_stderr; + for (int i = 0; i < (int)logs_.size(); i++) { logs_[i].id = i; } return init_info(&logs_[0]); @@ -47,17 +48,19 @@ class TsFileLogImpl : public LogInterface { private: struct Info { FileLog log; - bool is_inited = false; - int32 id; + std::atomic is_inited{false}; + int id; }; - static constexpr int32 MAX_THREAD_ID = 128; + static constexpr int MAX_THREAD_ID = 128; + td::int64 rotate_threshold_; + bool redirect_stderr_; std::string path_; std::array logs_; LogInterface *get_current_logger() { auto *info = get_current_info(); - if (!info->is_inited) { - init_info(info).ensure(); + if (!info->is_inited.load(std::memory_order_relaxed)) { + CHECK(init_info(info).is_ok()); } return &info->log; } @@ -67,25 +70,31 @@ class TsFileLogImpl : public LogInterface { } Status init_info(Info *info) { - TRY_STATUS(info->log.init(get_path(info), std::numeric_limits::max(), info->id == 0)); + TRY_STATUS(info->log.init(get_path(info), std::numeric_limits::max(), info->id == 0 && redirect_stderr_)); info->is_inited = true; return Status::OK(); } - string get_path(const Info *info) { + string get_path(Info *info) { if (info->id == 0) { return path_; } - return PSTRING() << path_ << "." << info->id; + return PSTRING() << path_ << ".thread" << info->id << ".log"; + } + + void rotate() override { + for (auto &info : logs_) { + if (info.is_inited.load(std::memory_order_consume)) { + info.log.lazy_rotate(); + } + } } }; - } // namespace detail -Result> TsFileLog::create(string path) { - auto res = make_unique(); - TRY_STATUS(res->init(path)); +Result> TsFileLog::create(string path, td::int64 rotate_threshold, bool redirect_stderr) { + auto res = td::make_unique(); + TRY_STATUS(res->init(path, rotate_threshold, redirect_stderr)); return std::move(res); } - } // namespace td diff --git a/tdutils/td/utils/TsFileLog.h b/tdutils/td/utils/TsFileLog.h index b3d239637..563e5d811 100644 --- a/tdutils/td/utils/TsFileLog.h +++ b/tdutils/td/utils/TsFileLog.h @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #pragma once #include "td/utils/common.h" @@ -11,10 +12,11 @@ #include "td/utils/Status.h" namespace td { - class TsFileLog { - public: - static Result> create(string path); -}; + static constexpr int64 DEFAULT_ROTATE_THRESHOLD = 10 * (1 << 20); + public: + static Result> create(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD, + bool redirect_stderr = true); +}; } // namespace td diff --git a/tdutils/td/utils/TsList.h b/tdutils/td/utils/TsList.h index c398489cc..fde1ec891 100644 --- a/tdutils/td/utils/TsList.h +++ b/tdutils/td/utils/TsList.h @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #pragma once #include "td/utils/common.h" diff --git a/tdutils/td/utils/Variant.h b/tdutils/td/utils/Variant.h index edee268a6..77c569334 100644 --- a/tdutils/td/utils/Variant.h +++ b/tdutils/td/utils/Variant.h @@ -241,6 +241,10 @@ class Variant { return offset_; } + bool empty() const { + return offset_ == npos; + } + private: union { int64 align_; diff --git a/tdutils/td/utils/VectorQueue.h b/tdutils/td/utils/VectorQueue.h index f23160255..c71ffeae5 100644 --- a/tdutils/td/utils/VectorQueue.h +++ b/tdutils/td/utils/VectorQueue.h @@ -32,6 +32,12 @@ class VectorQueue { read_pos_ += n; try_shrink(); } + T &front() { + return vector_[read_pos_]; + } + T &back() { + return vector_.back(); + } const T &front() const { return vector_[read_pos_]; } @@ -47,6 +53,9 @@ class VectorQueue { const T *data() const { return vector_.data() + read_pos_; } + T *data() { + return vector_.data() + read_pos_; + } Span as_span() const { return {data(), size()}; } diff --git a/tdutils/td/utils/base64.cpp b/tdutils/td/utils/base64.cpp index 7ae1045a5..1ede0bafa 100644 --- a/tdutils/td/utils/base64.cpp +++ b/tdutils/td/utils/base64.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/base64.h" #include "td/utils/common.h" @@ -236,4 +237,65 @@ string base64_filter(Slice input) { return res; } +static const char *const symbols32_lc = "abcdefghijklmnopqrstuvwxyz234567"; +static const char *const symbols32_uc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +string base32_encode(Slice input, bool upper_case) { + auto *symbols32 = (upper_case ? symbols32_uc : symbols32_lc); + string base32; + base32.reserve((input.size() * 8 + 4) / 5); + uint32 c = 0; + uint32 length = 0; + for (size_t i = 0; i < input.size(); i++) { + c = (c << 8) | input.ubegin()[i]; + length += 8; + while (length >= 5) { + length -= 5; + base32.push_back(symbols32[(c >> length) & 31]); + } + } + if (length != 0) { + base32.push_back(symbols32[(c << (5 - length)) & 31]); + } + //TODO: optional padding + return base32; +} + +static unsigned char b32_char_to_value[256]; +static void init_base32_table() { + static bool is_inited = [] { + std::fill(std::begin(b32_char_to_value), std::end(b32_char_to_value), static_cast(32)); + for (unsigned char i = 0; i < 32; i++) { + b32_char_to_value[static_cast(symbols32_lc[i])] = i; + b32_char_to_value[static_cast(symbols32_uc[i])] = i; + } + return true; + }(); + CHECK(is_inited); +} + +Result base32_decode(Slice base32) { + init_base32_table(); + string res; + res.reserve(base32.size() * 5 / 8); + uint32 c = 0; + uint32 length = 0; + for (size_t i = 0; i < base32.size(); i++) { + auto value = b32_char_to_value[base32.ubegin()[i]]; + if (value == 32) { + return Status::Error("Wrong character in the string"); + } + c = (c << 5) | value; + length += 5; + while (length >= 8) { + length -= 8; + res.push_back(td::uint8((c >> length) & 255)); + } + } + if ((c & ((1 << length) - 1)) != 0) { + return Status::Error("Nonzero padding"); + } + //TODO: check padding + return res; +} } // namespace td diff --git a/tdutils/td/utils/base64.h b/tdutils/td/utils/base64.h index 05ef1c7d3..1f60b2a73 100644 --- a/tdutils/td/utils/base64.h +++ b/tdutils/td/utils/base64.h @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #pragma once #include "td/utils/common.h" @@ -27,5 +28,6 @@ bool is_base64_characters(Slice input); bool is_base64url_characters(Slice input); string base64_filter(Slice input); - +string base32_encode(Slice input, bool upper_case = false); +Result base32_decode(Slice base32); } // namespace td diff --git a/tdutils/td/utils/bits.h b/tdutils/td/utils/bits.h index 090f31d27..aa355da74 100644 --- a/tdutils/td/utils/bits.h +++ b/tdutils/td/utils/bits.h @@ -136,6 +136,7 @@ inline uint64 bswap64(uint64 x) { } inline int32 count_bits32(uint32 x) { + // Do not use __popcnt because it will fail on some platforms. x -= (x >> 1) & 0x55555555; x = (x & 0x33333333) + ((x >> 2) & 0x33333333); x = (x + (x >> 4)) & 0x0F0F0F0F; @@ -269,4 +270,40 @@ inline int32 count_bits64(uint64 x) { #endif +struct BitsRange { + td::uint64 bits{0}; + mutable td::int32 pos{-1}; + + explicit BitsRange(td::uint64 bits = 0) : bits{bits}, pos{-1} { + } + + BitsRange begin() const { + return *this; + } + + BitsRange end() const { + return BitsRange{}; + } + + td::int32 operator*() const { + if (pos == -1) { + pos = td::count_trailing_zeroes64(bits); + } + return pos; + } + + bool operator!=(const BitsRange &other) const { + return bits != other.bits; + } + + BitsRange &operator++() { + auto i = **this; + if (i != 64) { + bits ^= 1ull << i; + } + pos = -1; + return *this; + } +}; + } // namespace td diff --git a/tdutils/td/utils/crypto.cpp b/tdutils/td/utils/crypto.cpp index 84d7885da..604b4fccd 100644 --- a/tdutils/td/utils/crypto.cpp +++ b/tdutils/td/utils/crypto.cpp @@ -606,16 +606,16 @@ void aes_cbc_decrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlic aes_cbc_xcrypt(aes_key, aes_iv, from, to, false); } -AesCbcState::AesCbcState(Slice key256, Slice iv128) : key_(key256), iv_(iv128) { - CHECK(key_.size() == 32); - CHECK(iv_.size() == 16); +AesCbcState::AesCbcState(Slice key256, Slice iv128) : raw_{td::SecureString(key256), td::SecureString(iv128)} { + CHECK(raw_.key.size() == 32); + CHECK(raw_.iv.size() == 16); } void AesCbcState::encrypt(Slice from, MutableSlice to) { - ::td::aes_cbc_encrypt(key_.as_slice(), iv_.as_mutable_slice(), from, to); + ::td::aes_cbc_encrypt(raw_.key.as_slice(), raw_.iv.as_mutable_slice(), from, to); } void AesCbcState::decrypt(Slice from, MutableSlice to) { - ::td::aes_cbc_decrypt(key_.as_slice(), iv_.as_mutable_slice(), from, to); + ::td::aes_cbc_decrypt(raw_.key.as_slice(), raw_.iv.as_mutable_slice(), from, to); } class AesCtrState::Impl { diff --git a/tdutils/td/utils/crypto.h b/tdutils/td/utils/crypto.h index ef62ee790..7c924c383 100644 --- a/tdutils/td/utils/crypto.h +++ b/tdutils/td/utils/crypto.h @@ -10,6 +10,7 @@ #include "td/utils/common.h" #include "td/utils/SharedSlice.h" #include "td/utils/Slice.h" +#include "td/utils/SharedSlice.h" #include "td/utils/Status.h" namespace td { @@ -94,10 +95,17 @@ class AesCbcState { void encrypt(Slice from, MutableSlice to); void decrypt(Slice from, MutableSlice to); + struct Raw { + SecureString key; + SecureString iv; + }; + + const Raw &raw() const { + return raw_; + } private: - SecureString key_; - SecureString iv_; + Raw raw_; }; void sha1(Slice data, unsigned char output[20]); diff --git a/tdutils/td/utils/filesystem.cpp b/tdutils/td/utils/filesystem.cpp index 092fcb1b3..47f047863 100644 --- a/tdutils/td/utils/filesystem.cpp +++ b/tdutils/td/utils/filesystem.cpp @@ -11,6 +11,7 @@ #include "td/utils/misc.h" #include "td/utils/PathView.h" #include "td/utils/port/FileFd.h" +#include "td/utils/port/path.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" #include "td/utils/unicode.h" @@ -32,7 +33,6 @@ template <> BufferSlice create_empty(size_t size) { return BufferSlice{size}; } - template <> SecureString create_empty(size_t size) { return SecureString{size}; @@ -41,16 +41,20 @@ SecureString create_empty(size_t size) { template Result read_file_impl(CSlice path, int64 size, int64 offset) { TRY_RESULT(from_file, FileFd::open(path, FileFd::Read)); + TRY_RESULT(file_size, from_file.get_size()); + if (offset < 0 || offset > file_size) { + return Status::Error("Failed to read file: invalid offset"); + } if (size == -1) { - TRY_RESULT_ASSIGN(size, from_file.get_size()); + size = file_size - offset; + } else if (size >= 0) { + if (size + offset > file_size) { + size = file_size - offset; + } } if (size < 0) { return Status::Error("Failed to read file: invalid size"); } - if (offset < 0 || offset > size) { - return Status::Error("Failed to read file: invalid offset"); - } - size -= offset; auto content = create_empty(narrow_cast(size)); TRY_RESULT(got_size, from_file.pread(as_mutable_slice(content), offset)); if (got_size != static_cast(size)) { @@ -80,13 +84,23 @@ Status copy_file(CSlice from, CSlice to, int64 size) { return write_file(to, content.as_slice()); } -Status write_file(CSlice to, Slice data) { +Status write_file(CSlice to, Slice data, WriteFileOptions options) { auto size = data.size(); TRY_RESULT(to_file, FileFd::open(to, FileFd::Truncate | FileFd::Create | FileFd::Write)); + if (options.need_lock) { + TRY_STATUS(to_file.lock(FileFd::LockFlags::Write, to.str(), 10)); + TRY_STATUS(to_file.truncate_to_current_position(0)); + } TRY_RESULT(written, to_file.write(data)); if (written != size) { return Status::Error(PSLICE() << "Failed to write file: written " << written << " bytes instead of " << size); } + if (options.need_sync) { + TRY_STATUS(to_file.sync()); + } + if (options.need_lock) { + to_file.lock(FileFd::LockFlags::Unlock, to.str(), 10).ignore(); + } to_file.close(); return Status::OK(); } @@ -166,4 +180,17 @@ string clean_filename(CSlice name) { return filename; } +Status atomic_write_file(CSlice path, Slice data, CSlice path_tmp) { + string path_tmp_buf; + if (path_tmp.empty()) { + path_tmp_buf = path.str() + ".tmp"; + path_tmp = path_tmp_buf; + } + + WriteFileOptions options; + options.need_sync = true; + options.need_lock = true; + TRY_STATUS(write_file(path_tmp, data, options)); + return rename(path_tmp, path); +} } // namespace td diff --git a/tdutils/td/utils/filesystem.h b/tdutils/td/utils/filesystem.h index eb8aad5a2..b1457f38e 100644 --- a/tdutils/td/utils/filesystem.h +++ b/tdutils/td/utils/filesystem.h @@ -9,18 +9,27 @@ #include "td/utils/buffer.h" #include "td/utils/SharedSlice.h" #include "td/utils/Slice.h" +#include "td/utils/SharedSlice.h" #include "td/utils/Status.h" namespace td { Result read_file(CSlice path, int64 size = -1, int64 offset = 0); -Result read_file_secure(CSlice path, int64 size = -1, int64 offset = 0); Result read_file_str(CSlice path, int64 size = -1, int64 offset = 0); +Result read_file_secure(CSlice path, int64 size = -1, int64 offset = 0); Status copy_file(CSlice from, CSlice to, int64 size = -1) TD_WARN_UNUSED_RESULT; -Status write_file(CSlice to, Slice data) TD_WARN_UNUSED_RESULT; +struct WriteFileOptions { + bool need_sync = true; + bool need_lock = true; +}; +Status write_file(CSlice to, Slice data, WriteFileOptions options = {}) TD_WARN_UNUSED_RESULT; string clean_filename(CSlice name); +// write file and ensure that it either fully overriden with new data, or left intact. +// Uses path_tmp to temporary storat data, than calls rename +Status atomic_write_file(CSlice path, Slice data, CSlice path_tmp = {}); + } // namespace td diff --git a/tdutils/td/utils/format.h b/tdutils/td/utils/format.h index 0cf0f8a21..ff98c07fd 100644 --- a/tdutils/td/utils/format.h +++ b/tdutils/td/utils/format.h @@ -13,6 +13,7 @@ #include #include +#include namespace td { namespace format { @@ -334,5 +335,9 @@ template StringBuilder &operator<<(StringBuilder &stream, const vector &vec) { return stream << format::as_array(vec); } +template +StringBuilder &operator<<(StringBuilder &stream, const std::set &vec) { + return stream << format::as_array(vec); +} } // namespace td diff --git a/tdutils/td/utils/invoke.h b/tdutils/td/utils/invoke.h index a79e1e409..122d85243 100644 --- a/tdutils/td/utils/invoke.h +++ b/tdutils/td/utils/invoke.h @@ -116,7 +116,7 @@ auto invoke(F &&f, } template -auto call_tuple_impl(F &func, std::tuple &&tuple, IntSeq) { +auto call_tuple_impl(F &&func, std::tuple &&tuple, IntSeq) { return func(std::forward(std::get(tuple))...); } @@ -151,7 +151,7 @@ class LogicAnd { }; template -auto call_tuple(F &func, std::tuple &&tuple) { +auto call_tuple(F &&func, std::tuple &&tuple) { return detail::call_tuple_impl(func, std::move(tuple), detail::IntRange()); } diff --git a/tdutils/td/utils/logging.cpp b/tdutils/td/utils/logging.cpp index b6e556ace..70214d906 100644 --- a/tdutils/td/utils/logging.cpp +++ b/tdutils/td/utils/logging.cpp @@ -14,6 +14,7 @@ #include #include +#include #if TD_ANDROID #include @@ -263,4 +264,26 @@ void process_fatal_error(CSlice message) { std::abort(); } +namespace { +std::mutex sdl_mutex; +int sdl_cnt = 0; +int sdl_verbosity = 0; + +} // namespace +ScopedDisableLog::ScopedDisableLog() { + std::unique_lock guard(sdl_mutex); + if (sdl_cnt == 0) { + sdl_verbosity = set_verbosity_level(std::numeric_limits::min()); + } + sdl_cnt++; +} + +ScopedDisableLog::~ScopedDisableLog() { + std::unique_lock guard(sdl_mutex); + sdl_cnt--; + if (sdl_cnt == 0) { + set_verbosity_level(sdl_verbosity); + } +} + } // namespace td diff --git a/tdutils/td/utils/logging.h b/tdutils/td/utils/logging.h index d7174ad13..4bfab52e0 100644 --- a/tdutils/td/utils/logging.h +++ b/tdutils/td/utils/logging.h @@ -40,8 +40,8 @@ #define VERBOSITY_NAME(x) verbosity_##x -#define GET_VERBOSITY_LEVEL() (::td::log_options.level) -#define SET_VERBOSITY_LEVEL(new_level) (::td::log_options.level = (new_level)) +#define GET_VERBOSITY_LEVEL() (::td::get_verbosity_level()) +#define SET_VERBOSITY_LEVEL(new_level) (::td::set_verbosity_level(new_level)) #ifndef STRIP_LOG #define STRIP_LOG VERBOSITY_NAME(DEBUG) @@ -52,7 +52,7 @@ #define LOGGER(interface, options, level, comment) ::td::Logger(interface, options, level, __FILE__, __LINE__, comment) #define LOG_IMPL_FULL(interface, options, strip_level, runtime_level, condition, comment) \ - LOG_IS_STRIPPED(strip_level) || runtime_level > options.level || !(condition) \ + LOG_IS_STRIPPED(strip_level) || runtime_level > options.get_level() || !(condition) \ ? (void)0 \ : ::td::detail::Voidify() & LOGGER(interface, options, runtime_level, comment) @@ -121,11 +121,18 @@ extern int VERBOSITY_NAME(files); extern int VERBOSITY_NAME(sqlite); struct LogOptions { - int level{VERBOSITY_NAME(DEBUG) + 1}; + std::atomic level{VERBOSITY_NAME(DEBUG) + 1}; bool fix_newlines{true}; bool add_info{true}; - static constexpr LogOptions plain() { + int get_level() const { + return level.load(std::memory_order_relaxed); + } + int set_level(int new_level) { + return level.exchange(new_level); + } + + static LogOptions plain() { return LogOptions{0, false, false}; } @@ -133,9 +140,31 @@ struct LogOptions { constexpr LogOptions(int level, bool fix_newlines, bool add_info) : level(level), fix_newlines(fix_newlines), add_info(add_info) { } + + LogOptions(const LogOptions &other) : LogOptions(other.level.load(), other.fix_newlines, other.add_info) { + } + + LogOptions &operator=(const LogOptions &other) { + level = other.level.load(); + fix_newlines = other.fix_newlines; + add_info = other.add_info; + return *this; + } }; extern LogOptions log_options; +inline int set_verbosity_level(int level) { + return log_options.set_level(level); +} +inline int get_verbosity_level() { + return log_options.get_level(); +} + +class ScopedDisableLog { + public: + ScopedDisableLog(); + ~ScopedDisableLog(); +}; class LogInterface { public: diff --git a/tdutils/td/utils/misc.h b/tdutils/td/utils/misc.h index 0d80ec630..e91c06ccd 100644 --- a/tdutils/td/utils/misc.h +++ b/tdutils/td/utils/misc.h @@ -32,12 +32,12 @@ std::pair split(T s, char delimiter = ' ') { } template -vector full_split(T s, char delimiter = ' ') { +vector full_split(T s, char delimiter = ' ', size_t max_parts = std::numeric_limits::max()) { vector result; if (s.empty()) { return result; } - while (true) { + while (result.size() + 1 < max_parts) { auto delimiter_pos = s.find(delimiter); if (delimiter_pos == string::npos) { result.push_back(std::move(s)); @@ -47,6 +47,8 @@ vector full_split(T s, char delimiter = ' ') { s = s.substr(delimiter_pos + 1); } } + result.push_back(std::move(s)); + return result; } string implode(const vector &v, char delimiter = ' '); @@ -193,10 +195,11 @@ inline char to_lower(char c) { return c; } -inline void to_lower_inplace(MutableSlice slice) { +inline MutableSlice to_lower_inplace(MutableSlice slice) { for (auto &c : slice) { c = to_lower(c); } + return slice; } inline string to_lower(Slice slice) { @@ -340,6 +343,20 @@ typename std::enable_if::value, T>::type hex_to_integer(Slic return integer_value; } +template +Result::value, T>::type> hex_to_integer_safe(Slice str) { + T integer_value = 0; + auto begin = str.begin(); + auto end = str.end(); + while (begin != end) { + if (!is_hex_digit(*begin)) { + return Status::Error("not a hex digit"); + } + integer_value = static_cast(integer_value * 16 + hex_to_int(*begin++)); + } + return integer_value; +} + double to_double(Slice str); template @@ -363,8 +380,8 @@ string url_encode(Slice data); namespace detail { template -struct is_same_signedness - : public std::integral_constant::value == std::is_signed::value> {}; +struct is_same_signedness : public std::integral_constant::value == std::is_signed::value> { +}; template struct safe_undeflying_type { diff --git a/tdutils/td/utils/optional.h b/tdutils/td/utils/optional.h index 314131624..639dfedbe 100644 --- a/tdutils/td/utils/optional.h +++ b/tdutils/td/utils/optional.h @@ -47,9 +47,11 @@ class optional { return impl_.is_ok(); } T &value() { + DCHECK(*this); return impl_.ok_ref(); } const T &value() const { + DCHECK(*this); return impl_.ok_ref(); } T &operator*() { @@ -62,6 +64,13 @@ class optional { return res; } + td::optional copy() const { + if (*this) { + return value(); + } + return {}; + } + template void emplace(ArgsT &&... args) { impl_.emplace(std::forward(args)...); diff --git a/tdutils/td/utils/port/FileFd.cpp b/tdutils/td/utils/port/FileFd.cpp index b7bd04168..5a464c45f 100644 --- a/tdutils/td/utils/port/FileFd.cpp +++ b/tdutils/td/utils/port/FileFd.cpp @@ -281,6 +281,17 @@ Result FileFd::read(MutableSlice slice) { #if TD_PORT_POSIX auto bytes_read = detail::skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); }); bool success = bytes_read >= 0; + if (!success) { + auto read_errno = errno; + if (read_errno == EAGAIN +#if EAGAIN != EWOULDBLOCK + || read_errno == EWOULDBLOCK +#endif + ) { + success = true; + bytes_read = 0; + } + } bool is_eof = success && narrow_cast(bytes_read) < slice.size(); #elif TD_PORT_WINDOWS DWORD bytes_read = 0; diff --git a/tdutils/td/utils/port/MemoryMapping.cpp b/tdutils/td/utils/port/MemoryMapping.cpp index 0bdf2c952..39c919850 100644 --- a/tdutils/td/utils/port/MemoryMapping.cpp +++ b/tdutils/td/utils/port/MemoryMapping.cpp @@ -82,7 +82,7 @@ Result MemoryMapping::create_from_file(const FileFd &file_fd, con auto fixed_begin = begin / page_size * page_size; auto data_offset = begin - fixed_begin; - auto data_size = narrow_cast(end - fixed_begin); + TRY_RESULT(data_size, narrow_cast_safe(end - fixed_begin)); void *data = mmap(nullptr, data_size, PROT_READ, MAP_PRIVATE, fd, narrow_cast(fixed_begin)); if (data == MAP_FAILED) { diff --git a/tdutils/td/utils/port/StdStreams.cpp b/tdutils/td/utils/port/StdStreams.cpp index 49983131c..c08f7650d 100644 --- a/tdutils/td/utils/port/StdStreams.cpp +++ b/tdutils/td/utils/port/StdStreams.cpp @@ -15,6 +15,7 @@ #include "td/utils/Slice.h" #include +#include namespace td { @@ -22,9 +23,7 @@ namespace td { template static FileFd &get_file_fd() { static FileFd result = FileFd::from_native_fd(NativeFd(id, true)); - static auto guard = ScopeExit() + [&] { - result.move_as_native_fd().release(); - }; + static auto guard = ScopeExit() + [&] { result.move_as_native_fd().release(); }; return result; } @@ -44,9 +43,7 @@ static FileFd &get_file_fd() { static auto handle = GetStdHandle(id); LOG_IF(FATAL, handle == INVALID_HANDLE_VALUE) << "Failed to GetStdHandle " << id; static FileFd result = FileFd::from_native_fd(NativeFd(handle, true)); - static auto guard = ScopeExit() + [&] { - result.move_as_native_fd().release(); - }; + static auto guard = ScopeExit() + [&] { result.move_as_native_fd().release(); }; #else static FileFd result; #endif diff --git a/tdutils/td/utils/port/detail/Epoll.cpp b/tdutils/td/utils/port/detail/Epoll.cpp index afa739017..75e419dcc 100644 --- a/tdutils/td/utils/port/detail/Epoll.cpp +++ b/tdutils/td/utils/port/detail/Epoll.cpp @@ -100,6 +100,7 @@ void Epoll::run(int timeout_ms) { #ifdef EPOLLRDHUP if (event->events & EPOLLRDHUP) { event->events &= ~EPOLLRDHUP; + flags = flags | PollFlags::Close(); // flags |= Fd::Close; // TODO } diff --git a/tdutils/td/utils/port/detail/ThreadPthread.cpp b/tdutils/td/utils/port/detail/ThreadPthread.cpp index 882d72056..2d7e4ef1a 100644 --- a/tdutils/td/utils/port/detail/ThreadPthread.cpp +++ b/tdutils/td/utils/port/detail/ThreadPthread.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/port/detail/ThreadPthread.h" char disable_linker_warning_about_empty_file_thread_pthread_cpp TD_UNUSED; diff --git a/tdutils/td/utils/port/detail/skip_eintr.h b/tdutils/td/utils/port/detail/skip_eintr.h index 3fe805ba5..7a1309270 100644 --- a/tdutils/td/utils/port/detail/skip_eintr.h +++ b/tdutils/td/utils/port/detail/skip_eintr.h @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #pragma once #include diff --git a/tdutils/td/utils/port/rlimit.cpp b/tdutils/td/utils/port/rlimit.cpp index 03a0babf4..3ff2cfc60 100644 --- a/tdutils/td/utils/port/rlimit.cpp +++ b/tdutils/td/utils/port/rlimit.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/port/rlimit.h" #include "td/utils/port/config.h" @@ -19,39 +20,76 @@ namespace td { #if TD_PORT_POSIX -static int get_resource(ResourceLimitType type) { - switch (type) { + +namespace { + +int get_rlimit_type(ResourceLimitType rlim_type) { + switch (rlim_type) { case ResourceLimitType::NoFile: return RLIMIT_NOFILE; + case ResourceLimitType::Rss: + return RLIMIT_RSS; default: UNREACHABLE(); - return -1; } } -#endif -Status set_resource_limit(ResourceLimitType type, uint64 value) { -#if TD_PORT_POSIX - int resource = get_resource(type); +} // namespace - rlimit rlim; - if (getrlimit(resource, &rlim) == -1) { - return OS_ERROR("Failed to get current resource limit"); +td::Status set_resource_limit(ResourceLimitType rlim_type, td::uint64 value, td::uint64 cap) { + if (cap && value > cap) { + return td::Status::Error("setrlimit(): bad argument"); + } + int resource = get_rlimit_type(rlim_type); + + struct rlimit r; + if (getrlimit(resource, &r) < 0) { + return td::Status::PosixError(errno, "failed getrlimit()"); } - TRY_RESULT(new_value, narrow_cast_safe(value)); - if (rlim.rlim_max < new_value) { - rlim.rlim_max = new_value; + if (cap) { + r.rlim_max = cap; + } else if (r.rlim_max < value) { + r.rlim_max = value; } - rlim.rlim_cur = new_value; - - if (setrlimit(resource, &rlim) < 0) { - return OS_ERROR("Failed to set resource limit"); + r.rlim_cur = value; + if (setrlimit(resource, &r) < 0) { + return td::Status::PosixError(errno, "failed setrlimit()"); } - return Status::OK(); -#elif TD_PORT_WINDOWS - return Status::OK(); // Windows has no limits -#endif + return td::Status::OK(); } +td::Status set_maximize_resource_limit(ResourceLimitType rlim_type, td::uint64 value) { + int resource = get_rlimit_type(rlim_type); + + struct rlimit r; + if (getrlimit(resource, &r) < 0) { + return td::Status::PosixError(errno, "failed getrlimit()"); + } + + if (r.rlim_max < value) { + auto t = r; + t.rlim_cur = value; + t.rlim_max = value; + if (setrlimit(resource, &t) >= 0) { + return td::Status::OK(); + } + } + + r.rlim_cur = value < r.rlim_max ? value : r.rlim_max; + if (setrlimit(resource, &r) < 0) { + return td::Status::PosixError(errno, "failed setrlimit()"); + } + return td::Status::OK(); +} +#else +td::Status set_resource_limit(ResourceLimitType rlim, td::uint64 value) { + return td::Status::Error("setrlimit not implemented on WINDOWS"); +} +td::Status set_maximize_resource_limit(ResourceLimitType rlim, td::uint64 value) { + return td::Status::OK(); +} +#endif + } // namespace td + diff --git a/tdutils/td/utils/port/rlimit.h b/tdutils/td/utils/port/rlimit.h index 5e858243d..c5ecddb45 100644 --- a/tdutils/td/utils/port/rlimit.h +++ b/tdutils/td/utils/port/rlimit.h @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #pragma once #include "td/utils/common.h" @@ -11,8 +12,8 @@ namespace td { -enum class ResourceLimitType { NoFile }; - -Status set_resource_limit(ResourceLimitType type, uint64 value); +enum class ResourceLimitType { NoFile, Rss }; +td::Status set_resource_limit(ResourceLimitType rlim_type, td::uint64 value, td::uint64 cap = 0); +td::Status set_maximize_resource_limit(ResourceLimitType rlim, td::uint64 value); } // namespace td diff --git a/tdutils/td/utils/port/signals.cpp b/tdutils/td/utils/port/signals.cpp index 78fec8f38..4658bc031 100644 --- a/tdutils/td/utils/port/signals.cpp +++ b/tdutils/td/utils/port/signals.cpp @@ -318,6 +318,9 @@ static void default_failure_signal_handler(int sig) { } Status set_default_failure_signal_handler() { +#if TD_PORT_POSIX + Stdin(); // init static variables before atexit +#endif std::atexit(block_stdin); TRY_STATUS(setup_signals_alt_stack()); TRY_STATUS(set_signal_handler(SignalType::Abort, default_failure_signal_handler)); diff --git a/tdutils/td/utils/port/user.cpp b/tdutils/td/utils/port/user.cpp index 4c5848e6e..598681674 100644 --- a/tdutils/td/utils/port/user.cpp +++ b/tdutils/td/utils/port/user.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/port/user.h" #include "td/utils/port/config.h" diff --git a/tdutils/td/utils/port/user.h b/tdutils/td/utils/port/user.h index ede8f094e..ea338851a 100644 --- a/tdutils/td/utils/port/user.h +++ b/tdutils/td/utils/port/user.h @@ -4,13 +4,12 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #pragma once #include "td/utils/Slice.h" #include "td/utils/Status.h" namespace td { - Status change_user(CSlice username, CSlice groupname = CSlice()); - } diff --git a/tdutils/td/utils/tests.cpp b/tdutils/td/utils/tests.cpp index fdf762dcd..350e09f53 100644 --- a/tdutils/td/utils/tests.cpp +++ b/tdutils/td/utils/tests.cpp @@ -192,6 +192,7 @@ bool TestsRunner::run_all_step() { } LOG(ERROR) << "Run test " << tag("name", name); state_.start = Time::now(); + state_.start_unadjusted = Time::now_unadjusted(); state_.is_running = true; } @@ -199,7 +200,13 @@ bool TestsRunner::run_all_step() { break; } - LOG(ERROR) << format::as_time(Time::now() - state_.start); + auto passed = Time::now() - state_.start; + auto real_passed = Time::now_unadjusted() - state_.start_unadjusted; + if (real_passed + 1e-9 > passed) { + LOG(ERROR) << format::as_time(passed); + } else { + LOG(ERROR) << format::as_time(passed) << " real[" << format::as_time(real_passed) << "]"; + } if (regression_tester_) { regression_tester_->save_db(); } diff --git a/tdutils/td/utils/tests.h b/tdutils/td/utils/tests.h index 717e44e7e..6f6fb211f 100644 --- a/tdutils/td/utils/tests.h +++ b/tdutils/td/utils/tests.h @@ -13,6 +13,7 @@ #include "td/utils/port/thread.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" +#include "td/utils/Span.h" #include "td/utils/Status.h" #include @@ -27,6 +28,34 @@ namespace td { +class RandomSteps { + public: + struct Step { + std::function func; + td::uint32 weight; + }; + RandomSteps(std::vector steps) : steps_(std::move(steps)) { + for (auto &step : steps_) { + steps_sum_ += step.weight; + } + } + template + void step(Random &rnd) { + auto w = rnd() % steps_sum_; + for (auto &step : steps_) { + if (w < step.weight) { + step.func(); + break; + } + w -= step.weight; + } + } + + private: + std::vector steps_; + td::int32 steps_sum_ = 0; +}; + class RegressionTester { public: virtual ~RegressionTester() = default; @@ -78,6 +107,7 @@ class TestsRunner : public TestContext { size_t it{0}; bool is_running = false; double start{0}; + double start_unadjusted{0}; size_t end{0}; }; bool stress_flag_{false}; @@ -135,36 +165,6 @@ inline vector rand_split(Slice str) { return res; } -struct Step { - std::function func; - uint32 weight; -}; - -class RandomSteps { - public: - explicit RandomSteps(vector steps) : steps_(std::move(steps)) { - for (const auto &step : steps_) { - steps_sum_ += step.weight; - } - } - - template - void step(Random &rnd) const { - auto w = rnd() % steps_sum_; - for (const auto &step : steps_) { - if (w < step.weight) { - step.func(); - break; - } - w -= step.weight; - } - } - - private: - vector steps_; - int32 steps_sum_ = 0; -}; - template void assert_eq_impl(const T1 &expected, const T2 &got, const char *file, int line) { LOG_CHECK(expected == got) << tag("expected", expected) << tag("got", got) << " in " << file << " at line " << line; diff --git a/tdutils/td/utils/tl_helpers.h b/tdutils/td/utils/tl_helpers.h index 382bf34f8..e0dbbbb65 100644 --- a/tdutils/td/utils/tl_helpers.h +++ b/tdutils/td/utils/tl_helpers.h @@ -11,6 +11,7 @@ #include "td/utils/misc.h" #include "td/utils/SharedSlice.h" #include "td/utils/Slice.h" +#include "td/utils/SharedSlice.h" #include "td/utils/StackAllocator.h" #include "td/utils/Status.h" #include "td/utils/tl_parsers.h" @@ -21,10 +22,10 @@ #include #include -#define BEGIN_STORE_FLAGS() \ - do { \ - uint32 flags_store = 0; \ - uint32 bit_offset_store = 0 +#define BEGIN_STORE_FLAGS() \ + do { \ + td::uint32 flags_store = 0; \ + td::uint32 bit_offset_store = 0 #define STORE_FLAG(flag) \ flags_store |= (flag) << bit_offset_store; \ @@ -36,10 +37,10 @@ } \ while (false) -#define BEGIN_PARSE_FLAGS() \ - do { \ - uint32 flags_parse; \ - uint32 bit_offset_parse = 0; \ +#define BEGIN_PARSE_FLAGS() \ + do { \ + td::uint32 flags_parse; \ + td::uint32 bit_offset_parse = 0; \ td::parse(flags_parse, parser) #define PARSE_FLAG(flag) \ diff --git a/tdutils/td/utils/tl_parsers.h b/tdutils/td/utils/tl_parsers.h index 89fa833bb..c9097f6de 100644 --- a/tdutils/td/utils/tl_parsers.h +++ b/tdutils/td/utils/tl_parsers.h @@ -70,6 +70,16 @@ class TlParser { } } + bool can_prefetch_int() const { + return get_left_len() >= sizeof(int32); + } + + int32 prefetch_int_unsafe() const { + int32 result; + std::memcpy(&result, data, sizeof(int32)); + return result; + } + int32 fetch_int_unsafe() { int32 result; std::memcpy(&result, data, sizeof(int32)); diff --git a/tdutils/td/utils/tl_storers.h b/tdutils/td/utils/tl_storers.h index 4476115b0..da6d36c6b 100644 --- a/tdutils/td/utils/tl_storers.h +++ b/tdutils/td/utils/tl_storers.h @@ -12,6 +12,8 @@ #include "td/utils/StorerBase.h" #include "td/utils/UInt.h" +#include "td/utils/SharedSlice.h" + #include namespace td { @@ -214,6 +216,12 @@ class TlStorerToString { store_field_end(); } + void store_field(const char *name, const SecureString &value) { + store_field_begin(name); + result.append(""); + store_field_end(); + } + template void store_field(const char *name, const T &value) { store_field_begin(name); @@ -221,6 +229,12 @@ class TlStorerToString { store_field_end(); } + void store_bytes_field(const char *name, const SecureString &value) { + store_field_begin(name); + result.append(""); + store_field_end(); + } + template void store_bytes_field(const char *name, const BytesT &value) { static const char *hex = "0123456789ABCDEF"; diff --git a/tdutils/td/utils/uint128.h b/tdutils/td/utils/uint128.h index c77a76b53..411db8f15 100644 --- a/tdutils/td/utils/uint128.h +++ b/tdutils/td/utils/uint128.h @@ -30,12 +30,18 @@ class uint128_emulated { uint64 lo() const { return lo_; } + uint64 rounded_hi() const { + return hi_ + (lo_ >> 63); + } static uint128 from_signed(int64 x) { if (x >= 0) { return uint128(0, x); } return uint128(std::numeric_limits::max(), static_cast(x)); } + static uint128 from_unsigned(uint64 x) { + return uint128(0, x); + } uint128 add(uint128 other) const { uint128 res(other.hi() + hi(), other.lo() + lo()); @@ -200,13 +206,18 @@ class uint128_intrinsic { static uint128 from_signed(int64 x) { return uint128(static_cast(x)); } + static uint128 from_unsigned(uint64 x) { + return uint128(static_cast(x)); + } uint64 hi() const { return uint64(value() >> 64); } uint64 lo() const { return uint64(value() & std::numeric_limits::max()); } - + uint64 rounded_hi() const { + return uint64((value() + (1ULL << 63)) >> 64); + } uint128 add(uint128 other) const { return uint128(value() + other.value()); } diff --git a/tdutils/test/ConcurrentHashMap.cpp b/tdutils/test/ConcurrentHashMap.cpp index 0045bff99..69067931c 100644 --- a/tdutils/test/ConcurrentHashMap.cpp +++ b/tdutils/test/ConcurrentHashMap.cpp @@ -252,4 +252,4 @@ TEST(ConcurrentHashMap, Benchmark) { #endif } -#endif \ No newline at end of file +#endif diff --git a/tdutils/test/List.cpp b/tdutils/test/List.cpp index 17a3fe184..fc64188a8 100644 --- a/tdutils/test/List.cpp +++ b/tdutils/test/List.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/common.h" #include "td/utils/List.h" #include "td/utils/MovableValue.h" diff --git a/tdutils/test/MpmcWaiter.cpp b/tdutils/test/MpmcWaiter.cpp index 8803bafd6..f6713a04e 100644 --- a/tdutils/test/MpmcWaiter.cpp +++ b/tdutils/test/MpmcWaiter.cpp @@ -13,21 +13,22 @@ #include #if !TD_THREAD_UNSUPPORTED -TEST(MpmcWaiter, stress_one_one) { +template +void test_waiter_stress_one_one() { td::Stage run; td::Stage check; std::vector threads; std::atomic value{0}; size_t write_cnt = 10; - td::unique_ptr waiter; + td::unique_ptr waiter; size_t threads_n = 2; for (size_t i = 0; i < threads_n; i++) { threads.push_back(td::thread([&, id = static_cast(i)] { for (td::uint64 round = 1; round < 100000; round++) { if (id == 0) { value = 0; - waiter = td::make_unique(); + waiter = td::make_unique(); write_cnt = td::Random::fast(1, 10); } run.wait(round * threads_n); @@ -37,17 +38,19 @@ TEST(MpmcWaiter, stress_one_one) { waiter->notify(); } } else { - int yields = 0; + typename W::Slot slot; + waiter->init_slot(slot, id); for (size_t i = 1; i <= write_cnt; i++) { while (true) { auto x = value.load(std::memory_order_relaxed); if (x >= i) { break; } - yields = waiter->wait(yields, id); + waiter->wait(slot); } - yields = waiter->stop_wait(yields, id); + waiter->stop_wait(slot); } + waiter->stop_wait(slot); } check.wait(round * threads_n); } @@ -57,7 +60,15 @@ TEST(MpmcWaiter, stress_one_one) { thread.join(); } } -TEST(MpmcWaiter, stress) { +TEST(MpmcEagerWaiter, stress_one_one) { + test_waiter_stress_one_one(); +} +TEST(MpmcSleepyWaiter, stress_one_one) { + test_waiter_stress_one_one(); +} + +template +void test_waiter_stress() { td::Stage run; td::Stage check; @@ -69,7 +80,7 @@ TEST(MpmcWaiter, stress) { size_t end_pos; size_t write_cnt; size_t threads_n = 20; - td::unique_ptr waiter; + td::unique_ptr waiter; for (size_t i = 0; i < threads_n; i++) { threads.push_back(td::thread([&, id = static_cast(i)] { for (td::uint64 round = 1; round < 1000; round++) { @@ -80,7 +91,7 @@ TEST(MpmcWaiter, stress) { end_pos = write_n * write_cnt; write_pos = 0; read_pos = 0; - waiter = td::make_unique(); + waiter = td::make_unique(); } run.wait(round * threads_n); if (id <= write_n) { @@ -92,21 +103,26 @@ TEST(MpmcWaiter, stress) { waiter->notify(); } } else if (id > 10 && id - 10 <= read_n) { - int yields = 0; + typename W::Slot slot; + waiter->init_slot(slot, id); while (true) { auto x = read_pos.load(std::memory_order_relaxed); if (x == end_pos) { + waiter->stop_wait(slot); break; } if (x == write_pos.load(std::memory_order_relaxed)) { - yields = waiter->wait(yields, id); + waiter->wait(slot); continue; } - yields = waiter->stop_wait(yields, id); + waiter->stop_wait(slot); read_pos.compare_exchange_strong(x, x + 1, std::memory_order_relaxed); } } check.wait(round * threads_n); + if (id == 0) { + waiter->close(); + } } })); } @@ -114,4 +130,10 @@ TEST(MpmcWaiter, stress) { thread.join(); } } +TEST(MpmcEagerWaiter, stress_multi) { + test_waiter_stress(); +} +TEST(MpmcSleepyWaiter, stress_multi) { + test_waiter_stress(); +} #endif // !TD_THREAD_UNSUPPORTED diff --git a/tdutils/test/OptionParser.cpp b/tdutils/test/OptionParser.cpp index 22815ca39..079a0bb19 100644 --- a/tdutils/test/OptionParser.cpp +++ b/tdutils/test/OptionParser.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/common.h" #include "td/utils/misc.h" #include "td/utils/OptionParser.h" @@ -47,23 +48,13 @@ TEST(OptionParser, run) { ASSERT_TRUE(result.is_error()); }; - options.add_option('q', "", "", [&] { - chosen_options += 1; - return td::Status::OK(); - }); - options.add_option('\0', "http-port2", "", [&] { - chosen_options += 10; - return td::Status::OK(); - }); + options.add_option('q', "", "", [&] { chosen_options += 1; }); + options.add_option('\0', "http-port2", "", [&] { chosen_options += 10; }); options.add_option('p', "http-port", "", [&](td::Slice parameter) { chosen_options += 100; chosen_parameters.push_back(parameter.str()); - return td::Status::OK(); - }); - options.add_option('v', "test", "", [&] { - chosen_options += 1000; - return td::Status::OK(); }); + options.add_option('v', "test", "", [&] { chosen_options += 1000; }); test_fail("-http-port2"); test_success("-", 0, {}, {"-"}); diff --git a/tdutils/test/SharedSlice.cpp b/tdutils/test/SharedSlice.cpp index aa8912cc3..33f7af795 100644 --- a/tdutils/test/SharedSlice.cpp +++ b/tdutils/test/SharedSlice.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/common.h" #include "td/utils/port/thread.h" #include "td/utils/SharedSlice.h" diff --git a/tdutils/test/heap.cpp b/tdutils/test/heap.cpp index b361b95dd..6d0ea0541 100644 --- a/tdutils/test/heap.cpp +++ b/tdutils/test/heap.cpp @@ -23,13 +23,9 @@ TEST(Heap, sort_random_perm) { for (int i = 0; i < n; i++) { v[i] = i; } - - // random shuffle - for (int i = 1; i < n; i++) { - std::swap(v[td::Random::fast(0, i)], v[i]); - } - - td::vector nodes(n); + td::Random::Xorshift128plus rnd(123); + td::random_shuffle(td::as_mutable_span(v), rnd); + std::vector nodes(n); td::KHeap kheap; for (int i = 0; i < n; i++) { kheap.insert(v[i], &nodes[i]); diff --git a/tdutils/test/log.cpp b/tdutils/test/log.cpp index 1503aafd2..416f85c1f 100644 --- a/tdutils/test/log.cpp +++ b/tdutils/test/log.cpp @@ -4,6 +4,7 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // + #include "td/utils/benchmark.h" #include "td/utils/FileLog.h" #include "td/utils/format.h" @@ -17,11 +18,6 @@ #include #include -// Thread safe logging with tests -// -// LOG uses thread local LogInterface -// void append(CSlice slice, int log_level); - char disable_linker_warning_about_empty_file_tdutils_test_log_cpp TD_UNUSED; #if !TD_THREAD_UNSUPPORTED @@ -56,6 +52,9 @@ class LogBenchmark : public td::Benchmark { void run_thread(int n) { auto str = PSTRING() << "#" << n << " : fsjklfdjsklfjdsklfjdksl\n"; for (int i = 0; i < n; i++) { + if (i % 10000 == 0) { + log_->rotate(); + } log_->append(str); } } @@ -74,7 +73,8 @@ static void bench_log(std::string name, int threads_n, F &&f) { }; TEST(Log, TsLogger) { - bench_log("NewTsFileLog", 4, [] { return td::TsFileLog::create("tmplog").move_as_ok(); }); + bench_log("NewTsFileLog", 4, + [] { return td::TsFileLog::create("tmplog", std::numeric_limits::max(), false).move_as_ok(); }); bench_log("TsFileLog", 8, [] { class FileLog : public td::LogInterface { public: diff --git a/tdutils/test/misc.cpp b/tdutils/test/misc.cpp index 423a44e6d..c109d2fb8 100644 --- a/tdutils/test/misc.cpp +++ b/tdutils/test/misc.cpp @@ -154,8 +154,7 @@ TEST(Misc, get_last_argument) { } TEST(Misc, call_n_arguments) { - auto f = [](int, int) { - }; + auto f = [](int, int) {}; call_n_arguments<2>(f, 1, 3, 4); } @@ -254,18 +253,10 @@ static void test_remove_if(vector v, const T &func, vector expected) { } TEST(Misc, remove_if) { - auto odd = [](int x) { - return x % 2 == 1; - }; - auto even = [](int x) { - return x % 2 == 0; - }; - auto all = [](int x) { - return true; - }; - auto none = [](int x) { - return false; - }; + auto odd = [](int x) { return x % 2 == 1; }; + auto even = [](int x) { return x % 2 == 0; }; + auto all = [](int x) { return true; }; + auto none = [](int x) { return false; }; vector v{1, 2, 3, 4, 5, 6}; test_remove_if(v, odd, {2, 4, 6}); @@ -348,6 +339,26 @@ TEST(Misc, contains) { ASSERT_TRUE(td::contains(str, 'c')); } +TEST(Misc, base32) { + ASSERT_EQ("", base32_encode("")); + ASSERT_EQ("me", base32_encode("a")); + base32_decode("me").ensure(); + ASSERT_EQ("mfra", base32_encode("ab")); + ASSERT_EQ("mfrgg", base32_encode("abc")); + ASSERT_EQ("mfrggza", base32_encode("abcd")); + ASSERT_EQ("mfrggzdg", base32_encode("abcdf")); + ASSERT_EQ("mfrggzdgm4", base32_encode("abcdfg")); + for (int l = 0; l < 300000; l += l / 20 + l / 1000 * 500 + 1) { + for (int t = 0; t < 10; t++) { + string s = rand_string(std::numeric_limits::min(), std::numeric_limits::max(), l); + auto encoded = base32_encode(s); + auto decoded = base32_decode(encoded); + ASSERT_TRUE(decoded.is_ok()); + ASSERT_TRUE(decoded.ok() == s); + } + } +} + TEST(Misc, to_integer) { ASSERT_EQ(to_integer("-1234567"), -1234567); ASSERT_EQ(to_integer("-1234567"), -1234567); @@ -721,6 +732,10 @@ static void test_full_split(Slice str, vector expected) { ASSERT_EQ(expected, td::full_split(str)); } +static void test_full_split(Slice str, char c, size_t max_parts, vector expected) { + ASSERT_EQ(expected, td::full_split(str, c, max_parts)); +} + TEST(Misc, full_split) { test_full_split("", {}); test_full_split(" ", {"", ""}); @@ -736,6 +751,7 @@ TEST(Misc, full_split) { test_full_split(" abcdef ", {"", "abcdef", ""}); test_full_split(" ab cd ef ", {"", "ab", "cd", "ef", ""}); test_full_split(" ab cd ef ", {"", "", "ab", "", "cd", "", "ef", "", ""}); + test_full_split("ab cd ef gh", ' ', 3, {"ab", "cd", "ef gh"}); } TEST(Misc, StringBuilder) { @@ -844,6 +860,39 @@ TEST(Misc, Bits) { ASSERT_EQ(4, count_bits64((1ull << 63) | 7)); } +TEST(Misc, BitsRange) { + auto to_vec_a = [](td::uint64 x) { + std::vector bits; + for (auto i : td::BitsRange(x)) { + bits.push_back(i); + } + return bits; + }; + + auto to_vec_b = [](td::uint64 x) { + std::vector bits; + td::int32 pos = 0; + while (x != 0) { + if ((x & 1) != 0) { + bits.push_back(pos); + } + x >>= 1; + pos++; + } + return bits; + }; + + auto do_check = [](std::vector a, std::vector b) { ASSERT_EQ(b, a); }; + auto check = [&](td::uint64 x) { do_check(to_vec_a(x), to_vec_b(x)); }; + + do_check(to_vec_a(21), {0, 2, 4}); + for (int x = 0; x < 100; x++) { + check(x); + check(std::numeric_limits::max() - x); + check(std::numeric_limits::max() - x); + } +} + #if !TD_THREAD_UNSUPPORTED TEST(Misc, Time) { Stage run; @@ -905,12 +954,8 @@ TEST(Misc, uint128) { static_cast(std::numeric_limits::min()) - 1}; #if TD_HAVE_INT128 - auto to_intrinsic = [](uint128_emulated num) { - return uint128_intrinsic(num.hi(), num.lo()); - }; - auto eq = [](uint128_emulated a, uint128_intrinsic b) { - return a.hi() == b.hi() && a.lo() == b.lo(); - }; + auto to_intrinsic = [](uint128_emulated num) { return uint128_intrinsic(num.hi(), num.lo()); }; + auto eq = [](uint128_emulated a, uint128_intrinsic b) { return a.hi() == b.hi() && a.lo() == b.lo(); }; auto ensure_eq = [&](uint128_emulated a, uint128_intrinsic b) { if (!eq(a, b)) { LOG(FATAL) << "[" << a.hi() << ";" << a.lo() << "] vs [" << b.hi() << ";" << b.lo() << "]"; @@ -1102,6 +1147,13 @@ TEST(Misc, CancellationToken) { CHECK(token4); } +TEST(Misc, Xorshift128plus) { + Random::Xorshift128plus rnd(123); + ASSERT_EQ(11453256657207062272ull, rnd()); + ASSERT_EQ(14917490455889357332ull, rnd()); + ASSERT_EQ(5645917797309401285ull, rnd()); + ASSERT_EQ(13554822455746959330ull, rnd()); +} TEST(Misc, uname) { auto first_version = get_operating_system_version(); auto second_version = get_operating_system_version(); diff --git a/test/main.cpp b/test/main.cpp index 09177f976..c7427d1b4 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -26,9 +26,9 @@ int main(int argc, char **argv) { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); td::OptionParser options; - options.add_option('\0', "filter", "Run only specified tests", + options.add_option('f', "filter", "Run only specified tests", [&](td::Slice filter) { runner.add_substr_filter(filter.str()); }); - options.add_option('\0', "stress", "Run tests infinitely", [&] { runner.set_stress_flag(true); }); + options.add_option('s', "stress", "Run tests infinitely", [&] { runner.set_stress_flag(true); }); auto r_non_options = options.run(argc, argv, 0); if (r_non_options.is_error()) { LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message();