update tdutils from another project

GitOrigin-RevId: 79b8eb2ba229d733f82dbb17b8bd7f27471c9472
This commit is contained in:
Arseny Smirnov 2020-06-24 14:47:36 +03:00
parent e8ac30cbbf
commit fd6423dedf
77 changed files with 1295 additions and 253 deletions

View File

@ -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<int>(GC_DELAY), static_cast<int>(GC_DELAY + GC_RAND_DELAY));
CHECK(next_gc_at >= sys_time);
auto next_gc_in = next_gc_at - sys_time;

View File

@ -102,7 +102,7 @@ struct FsFileInfo {
template <class CallbackT>
void scan_fs(CancellationToken &token, CallbackT &&callback) {
for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
int32 main_file_type = static_cast<size_t>(get_main_file_type(static_cast<FileType>(i)));
int32 main_file_type = static_cast<int32>(get_main_file_type(static_cast<FileType>(i)));
if (i != main_file_type) {
continue;
}

View File

@ -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)

View File

@ -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

View File

@ -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<Impl>(BN_le2bn(str.ubegin(), narrow_cast<int>(str.size()), nullptr)));
#elif OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
return BigNum(make_unique<Impl>(BN_lebin2bn(str.ubegin(), narrow_cast<int>(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);

View File

@ -72,6 +72,12 @@ class BufferedFd : public BufferedFdBase<FdT> {
~BufferedFd();
void close();
size_t left_unread() {
return input_reader_.size();
}
size_t left_unwritten() {
return output_reader_.size();
}
Result<size_t> flush_read(size_t max_read = std::numeric_limits<size_t>::max()) TD_WARN_UNUSED_RESULT;
Result<size_t> flush_write() TD_WARN_UNUSED_RESULT;

View File

@ -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<detail::RawCancellationToken> token) : token_(std::move(token)) {
}

View File

@ -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<int>::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<td::unique_ptr<LogInterface>> FileLog::create(string path, int64 rotate_threshold, bool redirect_stderr) {
auto l = make_unique<FileLog>();
TRY_STATUS(l->init(std::move(path), rotate_threshold, redirect_stderr));
return std::move(l);
}
} // namespace td

View File

@ -18,6 +18,8 @@ class FileLog : public LogInterface {
static constexpr int64 DEFAULT_ROTATE_THRESHOLD = 10 * (1 << 20);
public:
static Result<td::unique_ptr<LogInterface>> 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<bool> want_rotate_{};
void do_rotate();
};

View File

@ -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_;

View File

@ -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 <class T>
@ -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 <class T>
@ -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_ << " : ";

View File

@ -105,24 +105,28 @@ template <class T>
class OneValue<T *> {
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<T *> state_{nullptr};
T *Taken() {
static T xxx;
return &xxx;
std::atomic<T *> state_{Empty()};
static T *Empty() {
static int64 xxx;
return reinterpret_cast<T *>(&xxx);
}
static T *Taken() {
static int64 xxx;
return reinterpret_cast<T *>(&xxx);
}
};

View File

@ -7,58 +7,81 @@
#pragma once
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/port/thread.h"
#include <atomic>
#include <algorithm>
#include <condition_variable>
#include <mutex>
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<std::mutex> 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<uint32> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<int>(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<td::int32> state_{0};
std::mutex sleepers_mutex_;
std::vector<Slot *> sleepers_;
bool closed_ = false;
};
using MpmcWaiter = MpmcSleepyWaiter;
} // namespace td

View File

@ -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<Status(Slice)> 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)});
}

View File

@ -36,6 +36,10 @@ class OptionParser {
void add_checked_option(char short_key, Slice long_key, Slice description, std::function<Status(void)> callback);
void add_option(char short_key, Slice long_key, Slice description, std::function<Status(Slice)> callback) = delete;
void add_option(char short_key, Slice long_key, Slice description, std::function<Status(void)> callback) = delete;
void add_option(char short_key, Slice long_key, Slice description, std::function<void(Slice)> callback);
void add_option(char short_key, Slice long_key, Slice description, std::function<void(void)> callback);

View File

@ -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<int32>(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

View File

@ -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<int32>(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) {

View File

@ -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<int>(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<int>(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<double>(std::numeric_limits<td::uint32>::max()) - std::numeric_limits<td::uint32>::min()) *
(max - min);
}
Random::Xorshift128plus::Xorshift128plus(uint64 seed) {
auto next = [&] {
// splitmix64
seed += static_cast<uint64>(0x9E3779B97F4A7C15);
seed += static_cast<uint64>(0x9E3779B97F4A7C15ull);
uint64 z = seed;
z = (z ^ (z >> 30)) * static_cast<uint64>(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)) * static_cast<uint64>(0x94D049BB133111EB);
z = (z ^ (z >> 30)) * static_cast<uint64>(0xBF58476D1CE4E5B9ull);
z = (z ^ (z >> 27)) * static_cast<uint64>(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<int>((*this)() % (max - min + 1) + min);
}
int64 Random::Xorshift128plus::fast64(int64 min, int64 max) {
return static_cast<int64>((*this)() % (max - min + 1) + min);
}
void Random::Xorshift128plus::bytes(MutableSlice dest) {
int cnt = 0;

View File

@ -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 <class T, class R>
void random_shuffle(td::MutableSpan<T> v, R &rnd) {
for (std::size_t i = 1; i < v.size(); i++) {
auto pos = static_cast<std::size_t>(rnd() % (i + 1));
std::swap(v[i], v[pos]);
}
}
} // namespace td

View File

@ -37,9 +37,7 @@ class AtomicRefCnt {
};
template <class DataT, class DeleterT>
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 T, class DeleterT = std::default_delete<T>>
class SharedPtr {
public:
using Raw = detail::SharedPtrRaw<T, DeleterT>;
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) {

View File

@ -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"

View File

@ -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 <atomic>
@ -72,6 +72,9 @@ class UnsafeSharedSlice {
public:
UnsafeSharedSlice() = default;
UnsafeSharedSlice clone() const {
if (is_null()) {
return UnsafeSharedSlice();
}
header()->inc();
return UnsafeSharedSlice(ptr_.get());
}

View File

@ -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

View File

@ -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<InnerT *> rbegin() const {
return std::reverse_iterator<InnerT *>(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 <class T>
Span<T> span(const T *ptr, size_t size) {
return Span<T>(ptr, size);
}
template <class T>
Span<T> span(const std::vector<T> &vec) {
return Span<T>(vec);
}
template <class T>
MutableSpan<T> mutable_span(T *ptr, size_t size) {
return MutableSpan<T>(ptr, size);
}
template <class T>
MutableSpan<T> mutable_span(std::vector<T> &vec) {
return MutableSpan<T>(vec);
}
template <class T>
Span<T> span_one(const T &value) {
return td::Span<T>(&value, 1);
}
template <class T>
MutableSpan<T> mutable_span_one(T &value) {
return td::MutableSpan<T>(&value, 1);
}
template <class T>
Span<T> as_span(Span<T> span) {
return span;
}
template <class T>
Span<T> as_span(const std::vector<T> &vec) {
return Span<T>(vec);
}
template <class T>
MutableSpan<T> as_mutable_span(MutableSpan<T> span) {
return span;
}
template <class T>
MutableSpan<T> as_mutable_span(std::vector<T> &vec) {
return MutableSpan<T>(vec);
}
} // namespace td

View File

@ -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 T = Unit>
class Result {
public:
using ValueT = T;
Result() : status_(Status::Error<-1>()) {
}
template <class S, std::enable_if_t<!std::is_same<std::decay_t<S>, 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<T>();
}
template <class F>
td::Result<decltype(std::declval<F>()(std::declval<T>()))> move_map(F &&f) {
if (is_error()) {
return move_as_error();
}
return f(move_as_ok());
}
template <class F>
decltype(std::declval<F>()(std::declval<T>())) move_fmap(F &&f) {
if (is_error()) {
return move_as_error();
}
return f(move_as_ok());
}
private:
Status status_;
union {

View File

@ -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_;

View File

@ -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 <atomic>
namespace td {
template <class T>
class ThreadLocalStorage {
public:
@ -51,5 +51,4 @@ class ThreadLocalStorage {
return nodes_[thread_id];
}
};
} // namespace td

View File

@ -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 <mutex>
namespace td {
template <size_t N>
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<std::array<std::atomic<int64>, 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<std::mutex> 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;

View File

@ -7,14 +7,36 @@
#include "td/utils/Time.h"
#include <cmath>
#include <atomic>
namespace td {
bool operator==(Timestamp a, Timestamp b) {
return std::abs(a.at() - b.at()) < 1e-6;
}
namespace {
std::atomic<double> 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

View File

@ -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 <class StorerT>
void store(const Timestamp &timestamp, StorerT &storer) {
storer.store_binary(timestamp.at() - Time::now() + Clocks::system());

View File

@ -7,6 +7,7 @@
#pragma once
#include "td/utils/common.h"
#include "td/utils/optional.h"
#include <utility>
@ -68,4 +69,27 @@ class TimedStat {
}
};
template <class T, class Cmp>
struct MinMaxStat {
public:
using Event = T;
void on_event(Event event) {
if (!best_ || Cmp()(event, best_.value())) {
best_ = event;
}
}
td::optional<T> get_stat() const {
return best_.copy();
}
private:
td::optional<T> best_;
};
template <class T>
using MinStat = MinMaxStat<T, std::less<>>;
template <class T>
using MaxStat = MinMaxStat<T, std::greater<>>;
} // namespace td

View File

@ -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)

View File

@ -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 {

View File

@ -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 <limits>
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<int32>(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<bool> 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<Info, MAX_THREAD_ID> 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<int64>::max(), info->id == 0));
TRY_STATUS(info->log.init(get_path(info), std::numeric_limits<int64>::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<unique_ptr<LogInterface>> TsFileLog::create(string path) {
auto res = make_unique<detail::TsFileLogImpl>();
TRY_STATUS(res->init(path));
Result<td::unique_ptr<LogInterface>> TsFileLog::create(string path, td::int64 rotate_threshold, bool redirect_stderr) {
auto res = td::make_unique<detail::TsFileLog>();
TRY_STATUS(res->init(path, rotate_threshold, redirect_stderr));
return std::move(res);
}
} // namespace td

View File

@ -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<unique_ptr<LogInterface>> create(string path);
};
static constexpr int64 DEFAULT_ROTATE_THRESHOLD = 10 * (1 << 20);
public:
static Result<td::unique_ptr<LogInterface>> create(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD,
bool redirect_stderr = true);
};
} // namespace td

View File

@ -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"

View File

@ -241,6 +241,10 @@ class Variant {
return offset_;
}
bool empty() const {
return offset_ == npos;
}
private:
union {
int64 align_;

View File

@ -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<T> as_span() const {
return {data(), size()};
}

View File

@ -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<unsigned char>(32));
for (unsigned char i = 0; i < 32; i++) {
b32_char_to_value[static_cast<size_t>(symbols32_lc[i])] = i;
b32_char_to_value[static_cast<size_t>(symbols32_uc[i])] = i;
}
return true;
}();
CHECK(is_inited);
}
Result<string> 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

View File

@ -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<string> base32_decode(Slice base32);
} // namespace td

View File

@ -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

View File

@ -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 {

View File

@ -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]);

View File

@ -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<BufferSlice>(size_t size) {
return BufferSlice{size};
}
template <>
SecureString create_empty<SecureString>(size_t size) {
return SecureString{size};
@ -41,16 +41,20 @@ SecureString create_empty<SecureString>(size_t size) {
template <class T>
Result<T> 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<T>(narrow_cast<size_t>(size));
TRY_RESULT(got_size, from_file.pread(as_mutable_slice(content), offset));
if (got_size != static_cast<size_t>(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

View File

@ -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<BufferSlice> read_file(CSlice path, int64 size = -1, int64 offset = 0);
Result<SecureString> read_file_secure(CSlice path, int64 size = -1, int64 offset = 0);
Result<string> read_file_str(CSlice path, int64 size = -1, int64 offset = 0);
Result<SecureString> 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

View File

@ -13,6 +13,7 @@
#include <tuple>
#include <utility>
#include <set>
namespace td {
namespace format {
@ -334,5 +335,9 @@ template <class T>
StringBuilder &operator<<(StringBuilder &stream, const vector<T> &vec) {
return stream << format::as_array(vec);
}
template <class T>
StringBuilder &operator<<(StringBuilder &stream, const std::set<T> &vec) {
return stream << format::as_array(vec);
}
} // namespace td

View File

@ -116,7 +116,7 @@ auto invoke(F &&f,
}
template <class F, class... Args, std::size_t... S>
auto call_tuple_impl(F &func, std::tuple<Args...> &&tuple, IntSeq<S...>) {
auto call_tuple_impl(F &&func, std::tuple<Args...> &&tuple, IntSeq<S...>) {
return func(std::forward<Args>(std::get<S>(tuple))...);
}
@ -151,7 +151,7 @@ class LogicAnd {
};
template <class F, class... Args>
auto call_tuple(F &func, std::tuple<Args...> &&tuple) {
auto call_tuple(F &&func, std::tuple<Args...> &&tuple) {
return detail::call_tuple_impl(func, std::move(tuple), detail::IntRange<sizeof...(Args)>());
}

View File

@ -14,6 +14,7 @@
#include <atomic>
#include <cstdlib>
#include <mutex>
#if TD_ANDROID
#include <android/log.h>
@ -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<std::mutex> guard(sdl_mutex);
if (sdl_cnt == 0) {
sdl_verbosity = set_verbosity_level(std::numeric_limits<int>::min());
}
sdl_cnt++;
}
ScopedDisableLog::~ScopedDisableLog() {
std::unique_lock<std::mutex> guard(sdl_mutex);
sdl_cnt--;
if (sdl_cnt == 0) {
set_verbosity_level(sdl_verbosity);
}
}
} // namespace td

View File

@ -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<int> 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:

View File

@ -32,12 +32,12 @@ std::pair<T, T> split(T s, char delimiter = ' ') {
}
template <class T>
vector<T> full_split(T s, char delimiter = ' ') {
vector<T> full_split(T s, char delimiter = ' ', size_t max_parts = std::numeric_limits<size_t>::max()) {
vector<T> 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<T> full_split(T s, char delimiter = ' ') {
s = s.substr(delimiter_pos + 1);
}
}
result.push_back(std::move(s));
return result;
}
string implode(const vector<string> &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<std::is_unsigned<T>::value, T>::type hex_to_integer(Slic
return integer_value;
}
template <class T>
Result<typename std::enable_if<std::is_unsigned<T>::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<T>(integer_value * 16 + hex_to_int(*begin++));
}
return integer_value;
}
double to_double(Slice str);
template <class T>
@ -363,8 +380,8 @@ string url_encode(Slice data);
namespace detail {
template <class T, class U>
struct is_same_signedness
: public std::integral_constant<bool, std::is_signed<T>::value == std::is_signed<U>::value> {};
struct is_same_signedness : public std::integral_constant<bool, std::is_signed<T>::value == std::is_signed<U>::value> {
};
template <class T, class Enable = void>
struct safe_undeflying_type {

View File

@ -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<T> copy() const {
if (*this) {
return value();
}
return {};
}
template <class... ArgsT>
void emplace(ArgsT &&... args) {
impl_.emplace(std::forward<ArgsT>(args)...);

View File

@ -281,6 +281,17 @@ Result<size_t> 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<size_t>(bytes_read) < slice.size();
#elif TD_PORT_WINDOWS
DWORD bytes_read = 0;

View File

@ -82,7 +82,7 @@ Result<MemoryMapping> 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<size_t>(end - fixed_begin);
TRY_RESULT(data_size, narrow_cast_safe<size_t>(end - fixed_begin));
void *data = mmap(nullptr, data_size, PROT_READ, MAP_PRIVATE, fd, narrow_cast<off_t>(fixed_begin));
if (data == MAP_FAILED) {

View File

@ -15,6 +15,7 @@
#include "td/utils/Slice.h"
#include <atomic>
#include <cassert>
namespace td {
@ -22,9 +23,7 @@ namespace td {
template <int id>
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

View File

@ -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
}

View File

@ -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;

View File

@ -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 <cerrno>

View File

@ -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<rlim_t>(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

View File

@ -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

View File

@ -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));

View File

@ -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"

View File

@ -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());
}

View File

@ -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();
}

View File

@ -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 <atomic>
@ -27,6 +28,34 @@
namespace td {
class RandomSteps {
public:
struct Step {
std::function<void()> func;
td::uint32 weight;
};
RandomSteps(std::vector<Step> steps) : steps_(std::move(steps)) {
for (auto &step : steps_) {
steps_sum_ += step.weight;
}
}
template <class Random>
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<Step> 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<string> rand_split(Slice str) {
return res;
}
struct Step {
std::function<void()> func;
uint32 weight;
};
class RandomSteps {
public:
explicit RandomSteps(vector<Step> steps) : steps_(std::move(steps)) {
for (const auto &step : steps_) {
steps_sum_ += step.weight;
}
}
template <class Random>
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<Step> steps_;
int32 steps_sum_ = 0;
};
template <class T1, class T2>
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;

View File

@ -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 <unordered_set>
#include <utility>
#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) \

View File

@ -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));

View File

@ -12,6 +12,8 @@
#include "td/utils/StorerBase.h"
#include "td/utils/UInt.h"
#include "td/utils/SharedSlice.h"
#include <cstring>
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("<secret>");
store_field_end();
}
template <class T>
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("<secret>");
store_field_end();
}
template <class BytesT>
void store_bytes_field(const char *name, const BytesT &value) {
static const char *hex = "0123456789ABCDEF";

View File

@ -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<uint64>::max(), static_cast<uint64>(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<ValueT>(x));
}
static uint128 from_unsigned(uint64 x) {
return uint128(static_cast<ValueT>(x));
}
uint64 hi() const {
return uint64(value() >> 64);
}
uint64 lo() const {
return uint64(value() & std::numeric_limits<uint64>::max());
}
uint64 rounded_hi() const {
return uint64((value() + (1ULL << 63)) >> 64);
}
uint128 add(uint128 other) const {
return uint128(value() + other.value());
}

View File

@ -252,4 +252,4 @@ TEST(ConcurrentHashMap, Benchmark) {
#endif
}
#endif
#endif

View File

@ -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"

View File

@ -13,21 +13,22 @@
#include <atomic>
#if !TD_THREAD_UNSUPPORTED
TEST(MpmcWaiter, stress_one_one) {
template <class W>
void test_waiter_stress_one_one() {
td::Stage run;
td::Stage check;
std::vector<td::thread> threads;
std::atomic<size_t> value{0};
size_t write_cnt = 10;
td::unique_ptr<td::MpmcWaiter> waiter;
td::unique_ptr<W> waiter;
size_t threads_n = 2;
for (size_t i = 0; i < threads_n; i++) {
threads.push_back(td::thread([&, id = static_cast<td::uint32>(i)] {
for (td::uint64 round = 1; round < 100000; round++) {
if (id == 0) {
value = 0;
waiter = td::make_unique<td::MpmcWaiter>();
waiter = td::make_unique<W>();
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<td::MpmcEagerWaiter>();
}
TEST(MpmcSleepyWaiter, stress_one_one) {
test_waiter_stress_one_one<td::MpmcSleepyWaiter>();
}
template <class W>
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<td::MpmcWaiter> waiter;
td::unique_ptr<W> waiter;
for (size_t i = 0; i < threads_n; i++) {
threads.push_back(td::thread([&, id = static_cast<td::uint32>(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<td::MpmcWaiter>();
waiter = td::make_unique<W>();
}
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<td::MpmcEagerWaiter>();
}
TEST(MpmcSleepyWaiter, stress_multi) {
test_waiter_stress<td::MpmcSleepyWaiter>();
}
#endif // !TD_THREAD_UNSUPPORTED

View File

@ -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, {}, {"-"});

View File

@ -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"

View File

@ -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<td::HeapNode> nodes(n);
td::Random::Xorshift128plus rnd(123);
td::random_shuffle(td::as_mutable_span(v), rnd);
std::vector<td::HeapNode> nodes(n);
td::KHeap<int> kheap;
for (int i = 0; i < n; i++) {
kheap.insert(v[i], &nodes[i]);

View File

@ -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 <functional>
#include <limits>
// 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<td::int64>::max(), false).move_as_ok(); });
bench_log("TsFileLog", 8, [] {
class FileLog : public td::LogInterface {
public:

View File

@ -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<int> v, const T &func, vector<int> 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<int> 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<char>::min(), std::numeric_limits<char>::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<int32>("-1234567"), -1234567);
ASSERT_EQ(to_integer<int64>("-1234567"), -1234567);
@ -721,6 +732,10 @@ static void test_full_split(Slice str, vector<Slice> expected) {
ASSERT_EQ(expected, td::full_split(str));
}
static void test_full_split(Slice str, char c, size_t max_parts, vector<Slice> 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<td::int32> bits;
for (auto i : td::BitsRange(x)) {
bits.push_back(i);
}
return bits;
};
auto to_vec_b = [](td::uint64 x) {
std::vector<td::int32> 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<td::int32> a, std::vector<td::int32> 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<td::uint32>::max() - x);
check(std::numeric_limits<td::uint64>::max() - x);
}
}
#if !TD_THREAD_UNSUPPORTED
TEST(Misc, Time) {
Stage run;
@ -905,12 +954,8 @@ TEST(Misc, uint128) {
static_cast<int64>(std::numeric_limits<int32>::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();

View File

@ -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();