diff --git a/include/dsn/cpp/rpc_holder.h b/include/dsn/cpp/rpc_holder.h index ef14524109..f9417772ff 100644 --- a/include/dsn/cpp/rpc_holder.h +++ b/include/dsn/cpp/rpc_holder.h @@ -30,11 +30,13 @@ #include #include #include -#include -#include +#include +#include namespace dsn { +using literals::chrono_literals::operator"" _ms; + // // rpc_holder is mainly designed for RAII of dsn_message_t. // Since the request message will be automatically released after the rpc ends, @@ -69,6 +71,10 @@ namespace dsn { template class rpc_holder { +public: + using request_type = TRequest; + using response_type = TResponse; + public: explicit rpc_holder(dsn_message_t req = nullptr) { @@ -76,9 +82,18 @@ class rpc_holder _i = std::make_shared(req); } } - rpc_holder(std::unique_ptr req, dsn::task_code code) : _i(new internal(req, code)) {} + + rpc_holder(std::unique_ptr req, + dsn::task_code code, + std::chrono::milliseconds timeout = 0_ms, + uint64_t partition_hash = 0) + : _i(new internal(req, code, timeout, partition_hash)) + { + } // copyable and movable + // Copying an rpc_holder doesn't produce a deep copy, the new instance will + // reference the same rpc internal data. So, just feel free to copy :) rpc_holder(const rpc_holder &) = default; rpc_holder(rpc_holder &&) noexcept = default; rpc_holder &operator=(const rpc_holder &) = default; @@ -92,6 +107,12 @@ class rpc_holder return *(_i->thrift_request); } + TRequest *mutable_request() const + { + dassert(_i, "rpc_holder is uninitialized"); + return _i->thrift_request.get(); + } + TResponse &response() const { dassert(_i, "rpc_holder is uninitialized"); @@ -105,6 +126,8 @@ class rpc_holder } // TCallback = void(dsn::error_code) + // NOTE that the `error_code` is not the error carried by response. Users should + // check the responded error themselves. template task_ptr call(::dsn::rpc_address server, clientlet *svc, TCallback &&callback, int reply_thread_hash = 0) @@ -116,7 +139,7 @@ class rpc_holder dsn::error_code>::value, "the first argument of TCallback must be dsn::error_code"); - if (_mail_box) { + if (dsn_unlikely(_mail_box != nullptr)) { _mail_box->emplace_back(request()); return nullptr; } @@ -125,12 +148,9 @@ class rpc_holder dsn_request(), svc, [ cb_fwd = std::forward(callback), - this ](error_code err, dsn_message_t req, dsn_message_t resp) mutable { + rpc = *this ](error_code err, dsn_message_t req, dsn_message_t resp) mutable { if (err == ERR_OK) { - ::dsn::unmarshall(resp, response()); - } - if (response().err) { - err = response().err; + ::dsn::unmarshall(resp, rpc.response()); } cb_fwd(err); }, @@ -139,6 +159,16 @@ class rpc_holder return t; } + // Returns an rpc_holder that will reply the request after its lifetime ends. + // By default rpc_holder never replies. + // SEE: serverlet::register_rpc_handler_with_rpc_holder + static inline rpc_holder auto_reply(dsn_message_t req) + { + rpc_holder rpc(req); + rpc._i->auto_reply = true; + return rpc; + } + // Only use this function when testing. // In mock mode, all messages will be dropped into mail_box without going through network, // and response callbacks will never be called. @@ -168,7 +198,7 @@ class rpc_holder struct internal { explicit internal(dsn_message_t req) - : dsn_request(req), thrift_request(make_unique()) + : dsn_request(req), thrift_request(make_unique()), auto_reply(false) { // we must hold one reference for the request, or rdsn will delete it after // the rpc call ends. @@ -176,21 +206,41 @@ class rpc_holder dsn::unmarshall(req, *thrift_request); } - internal(std::unique_ptr &req, dsn::task_code code) - : thrift_request(std::move(req)) + internal(std::unique_ptr &req, + dsn::task_code code, + std::chrono::milliseconds timeout, + uint64_t partition_hash) + : thrift_request(std::move(req)), auto_reply(false) { dassert(thrift_request != nullptr, "req should not be null"); - dsn_request = dsn_msg_create_request(code); + // leave thread_hash to 0 + dsn_request = + dsn_msg_create_request(code, static_cast(timeout.count()), 0, partition_hash); dsn_msg_add_ref(dsn_request); dsn::marshall(dsn_request, *thrift_request); } - ~internal() { dsn_msg_release_ref(dsn_request); } + void reply() + { + dsn_message_t dsn_response = dsn_msg_create_response(dsn_request); + ::dsn::marshall(dsn_response, thrift_response); + dsn_rpc_reply(dsn_response); + } + + ~internal() + { + if (auto_reply) { + reply(); + } + dsn_msg_release_ref(dsn_request); + } dsn_message_t dsn_request; std::unique_ptr thrift_request; TResponse thrift_response; + + bool auto_reply; }; std::shared_ptr _i; diff --git a/include/dsn/cpp/serverlet.h b/include/dsn/cpp/serverlet.h index 779b46f939..ff85c8db7e 100644 --- a/include/dsn/cpp/serverlet.h +++ b/include/dsn/cpp/serverlet.h @@ -37,6 +37,7 @@ #include #include +#include namespace dsn { /*! @@ -122,6 +123,12 @@ class serverlet : public virtual clientlet void (T::*handler)(const TRequest &, TResponse &), dsn::gpid gpid = dsn::gpid()); + template + bool register_rpc_handler_with_rpc_holder(dsn::task_code rpc_code, + const char *rpc_description, + void (T::*handler)(TRpcHolder), + dsn::gpid gpid = {}); + template bool register_async_rpc_handler(dsn::task_code rpc_code, const char *rpc_name_, @@ -215,6 +222,26 @@ inline bool serverlet::register_rpc_handler(dsn::task_code rpc_code, return dsn_rpc_register_handler(rpc_code, rpc_name_, cb, hc, gpid); } +template +template +inline bool serverlet::register_rpc_handler_with_rpc_holder(dsn::task_code rpc_code, + const char *rpc_description, + void (T::*handler)(TRpcHolder), + dsn::gpid gpid) +{ + typedef handler_context hc_type; + auto hc = (hc_type *)malloc(sizeof(hc_type)); + hc->this_ = (T *)this; + hc->cb = handler; + + dsn_rpc_request_handler_t cb = [](dsn_message_t request, void *param) { + auto hc2 = (hc_type *)param; + ((hc2->this_)->*(hc2->cb))(TRpcHolder::auto_reply(request)); + }; + + return dsn_rpc_register_handler(rpc_code, rpc_description, cb, hc, gpid); +} + template template inline bool serverlet::register_async_rpc_handler(dsn::task_code rpc_code, diff --git a/include/dsn/dist/fmt_logging.h b/include/dsn/dist/fmt_logging.h new file mode 100644 index 0000000000..36b29bf72d --- /dev/null +++ b/include/dsn/dist/fmt_logging.h @@ -0,0 +1,77 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Microsoft Corporation + * + * -=- Robust Distributed System Nucleus (rDSN) -=- + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include +#include +#include + +// The macros below no longer use the default snprintf method for log message formatting, +// instead we use fmt::format. +// TODO(wutao1): prevent construction of std::string for each log. + +#define dinfo_f(...) dinfo(fmt::format(__VA_ARGS__).c_str()) +#define ddebug_f(...) ddebug(fmt::format(__VA_ARGS__).c_str()) +#define dwarn_f(...) dwarn(fmt::format(__VA_ARGS__).c_str()) +#define derror_f(...) derror(fmt::format(__VA_ARGS__).c_str()) +#define dfatal_f(...) dfatal(fmt::format(__VA_ARGS__).c_str()) +#define dassert_f(x, ...) dassert(x, fmt::format(__VA_ARGS__).c_str()) + +// Macros for writing log message prefixed by gpid. +#define dinfo_replica(...) dinfo_f("[gpid: {}] {}", get_gpid(), fmt::format(__VA_ARGS__)); +#define ddebug_replica(...) ddebug_f("[gpid: {}] {}", get_gpid(), fmt::format(__VA_ARGS__)); +#define dwarn_replica(...) dwarn_f("[gpid: {}] {}", get_gpid(), fmt::format(__VA_ARGS__)); +#define derror_replica(...) derror_f("[gpid: {}] {}", get_gpid(), fmt::format(__VA_ARGS__)); +#define dfatal_replica(...) dfatal_f("[gpid: {}] {}", get_gpid(), fmt::format(__VA_ARGS__)); + +// Customized formatter for rDSN basic types, on which +// users can easily call fmt::format("{}", xxx), without the effort +// of converting them into string. + +namespace fmt { + +inline void format_arg(fmt::BasicFormatter &f, const char *format_str, dsn::gpid p) +{ + f.writer().write("{}.{}", p.get_app_id(), p.get_partition_index()); +} + +inline void format_arg(fmt::BasicFormatter &f, const char *format_str, const dsn::error_s &p) +{ + f.writer().write(p.description()); +} + +inline void format_arg(fmt::BasicFormatter &f, const char *format_str, dsn::error_code p) +{ + f.writer().write(p.to_string()); +} + +inline void format_arg(fmt::BasicFormatter &f, const char *format_str, dsn::task_code p) +{ + f.writer().write(p.to_string()); +} + +} // namespace fmt diff --git a/include/dsn/tool-api/gpid.h b/include/dsn/tool-api/gpid.h index 12603167a9..41a7463c51 100644 --- a/include/dsn/tool-api/gpid.h +++ b/include/dsn/tool-api/gpid.h @@ -32,17 +32,16 @@ #endif namespace dsn { + +// Group-Partition-ID. class gpid { public: - gpid(int app_id, int pidx) - { - _value.u.app_id = app_id; - _value.u.partition_index = pidx; - } - gpid(const gpid &gd) { _value.value = gd._value.value; } - gpid() { _value.value = 0; } - uint64_t value() const { return _value.value; } + constexpr gpid(int app_id, int pidx) : _value({.u = {app_id, pidx}}) {} + + constexpr gpid() = default; + + constexpr uint64_t value() const { return _value.value; } bool operator<(const gpid &r) const { @@ -50,15 +49,23 @@ class gpid (_value.u.app_id == r._value.u.app_id && _value.u.partition_index < r._value.u.partition_index); } - bool operator==(const gpid &r) const { return value() == r.value(); } - bool operator!=(const gpid &r) const { return value() != r.value(); } - int32_t get_app_id() const { return _value.u.app_id; } - int32_t get_partition_index() const { return _value.u.partition_index; } + constexpr bool operator==(const gpid &r) const { return value() == r.value(); } + + constexpr bool operator!=(const gpid &r) const { return value() != r.value(); } + + constexpr int32_t get_app_id() const { return _value.u.app_id; } + + constexpr int32_t get_partition_index() const { return _value.u.partition_index; } + void set_app_id(int32_t v) { _value.u.app_id = v; } + void set_partition_index(int32_t v) { _value.u.partition_index = v; } + void set_value(uint64_t v) { _value.value = v; } + bool parse_from(const char *str); + const char *to_string() const; #ifdef DSN_USE_THRIFT_SERIALIZATION @@ -67,6 +74,7 @@ class gpid #endif int thread_hash() const { return _value.u.app_id * 7919 + _value.u.partition_index; } + private: union { @@ -76,9 +84,10 @@ class gpid int32_t partition_index; ///< zero-based partition index } u; uint64_t value; - } _value; + } _value{.value = 0}; }; -} + +} // namespace dsn namespace std { template <> diff --git a/include/dsn/tool-api/task_code.h b/include/dsn/tool-api/task_code.h index e73afad74d..fb43fa0995 100644 --- a/include/dsn/tool-api/task_code.h +++ b/include/dsn/tool-api/task_code.h @@ -69,13 +69,22 @@ ENUM_REG(TASK_PRIORITY_HIGH) ENUM_END(dsn_task_priority_t) namespace dsn { -// task code is an index for a specific kind of task. with the index, you can -// get properties of this kind of task: name, type, priority, etc. you may want to refer to -// task_spec.h for the detailed task properties. -// -// for performance, task_code is represented as an integer in memory; and for compatibility, -// task_code is serialized as it's string representation when transfered by network and stored in -// disk. + +/// task code is an index for a specific kind of task. with the index, you can +/// get properties of this kind of task: name, type, priority, etc. you may want to refer to +/// task_spec.h for the detailed task properties. +/// +/// Like dsn::blob, task_code is a special thrift primitive type that's defined +/// by the rDSN framework. Internally as a C++ object, it's is represented as an integer, +/// but in thrift representation it's serialized as a string. +/// +/// It should be noted that a task_code may have different code number in two different +/// clusters. So DO NOT use a integer as task_code. +/// +/// **.thrift +/// x: 1: i32 task_code; +/// ✓: 1: dsn.task_code task_code; +/// class task_code { public: @@ -83,21 +92,21 @@ class task_code dsn_task_type_t tt, dsn_task_priority_t pri, dsn::threadpool_code pool); - task_code() { _internal_code = 0; } - task_code(const task_code &r) { _internal_code = r._internal_code; } - explicit task_code(int code) { _internal_code = code; } + + constexpr task_code() = default; + + constexpr explicit task_code(int code) : _internal_code(code) {} const char *to_string() const; - task_code &operator=(const task_code &source) - { - _internal_code = source._internal_code; - return *this; - } - bool operator==(const task_code &r) { return _internal_code == r._internal_code; } - bool operator!=(const task_code &r) { return !(*this == r); } - operator int() const { return _internal_code; } - int code() const { return _internal_code; } + constexpr bool operator==(const task_code &r) { return _internal_code == r._internal_code; } + + constexpr bool operator!=(const task_code &r) { return !(*this == r); } + + constexpr operator int() const { return _internal_code; } + + constexpr int code() const { return _internal_code; } + #ifdef DSN_USE_THRIFT_SERIALIZATION uint32_t read(::apache::thrift::protocol::TProtocol *iprot); uint32_t write(::apache::thrift::protocol::TProtocol *oprot) const; @@ -110,7 +119,7 @@ class task_code private: task_code(const char *name); - int _internal_code; + int _internal_code{0}; }; // you can define task_cods by the following macros diff --git a/include/dsn/utility/blob.h b/include/dsn/utility/blob.h index 8e8c273eb3..a55ceee8c0 100644 --- a/include/dsn/utility/blob.h +++ b/include/dsn/utility/blob.h @@ -1,3 +1,29 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Microsoft Corporation + * + * -=- Robust Distributed System Nucleus (rDSN) -=- + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + #pragma once #include @@ -7,10 +33,13 @@ namespace dsn { +/// dsn::blob is a special thrift type that's not generated by thrift compiler, +/// but defined by the rDSN framework. Unlike thrift `string`, dsn::blob is +/// implemented by ref-counted buffer. class blob { public: - blob() : _buffer(nullptr), _data(nullptr), _length(0) {} + constexpr blob() = default; blob(const std::shared_ptr &buffer, unsigned int length) : _holder(buffer), _buffer(_holder.get()), _data(_holder.get()), _length(length) @@ -35,51 +64,14 @@ class blob { } + /// NOTE: Use dsn::string_view whenever possible. + /// blob is designed for shared buffer, never use it as constant view. + /// Maybe we could deprecate this function in the future. blob(const char *buffer, int offset, unsigned int length) : _buffer(buffer), _data(buffer + offset), _length(length) { } - blob(const blob &source) - : _holder(source._holder), - _buffer(source._buffer), - _data(source._data), - _length(source._length) - { - } - - blob(blob &&source) - : _holder(std::move(source._holder)), - _buffer(source._buffer), - _data(source._data), - _length(source._length) - { - source._buffer = nullptr; - source._data = nullptr; - source._length = 0; - } - - blob &operator=(const blob &that) - { - _holder = that._holder; - _buffer = that._buffer; - _data = that._data; - _length = that._length; - return *this; - } - - blob &operator=(blob &&that) - { - _holder = std::move(that._holder); - _buffer = that._buffer; - _data = that._data; - _length = that._length; - that._buffer = nullptr; - that._data = nullptr; - that._length = 0; - return *this; - } - void assign(const std::shared_ptr &buffer, int offset, unsigned int length) { _holder = buffer; @@ -104,9 +96,9 @@ class blob _length = length; } - const char *data() const { return _data; } + const char *data() const noexcept { return _data; } - unsigned int length() const { return _length; } + unsigned int length() const noexcept { return _length; } std::shared_ptr buffer() const { return _holder; } @@ -148,6 +140,13 @@ class blob return false; } + std::string to_string() const + { + if (_length == 0) + return {}; + return std::string(_data, _length); + } + #ifdef DSN_USE_THRIFT_SERIALIZATION uint32_t read(::apache::thrift::protocol::TProtocol *iprot); uint32_t write(::apache::thrift::protocol::TProtocol *oprot) const; @@ -155,8 +154,8 @@ class blob private: friend class binary_writer; std::shared_ptr _holder; - const char *_buffer; - const char *_data; - unsigned int _length; // data length + const char *_buffer{nullptr}; + const char *_data{nullptr}; + unsigned int _length{0}; // data length }; } diff --git a/include/dsn/utility/chrono_literals.h b/include/dsn/utility/chrono_literals.h new file mode 100644 index 0000000000..250e73ed52 --- /dev/null +++ b/include/dsn/utility/chrono_literals.h @@ -0,0 +1,78 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Microsoft Corporation + * + * -=- Robust Distributed System Nucleus (rDSN) -=- + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include + +/// This is a simple implementation of chrono literals +/// (http://en.cppreference.com/w/cpp/chrono/duration#Literals). +/// Deprecate this when we have our compiler version updated to gcc-5 +/// (https://en.cppreference.com/w/cpp/compiler_support). + +namespace dsn { + +/// Example: +/// +/// using namespace dsn::literals::chrono_literals; +/// +/// template +/// void schedule(F &&f, std::chrono::milliseconds delay_ms = 0_ms); +/// + +inline namespace literals { +inline namespace chrono_literals { + +constexpr std::chrono::hours operator"" _h(unsigned long long v) { return std::chrono::hours{v}; } + +constexpr std::chrono::minutes operator"" _min(unsigned long long v) +{ + return std::chrono::minutes{v}; +} + +constexpr std::chrono::seconds operator"" _s(unsigned long long v) +{ + return std::chrono::seconds{v}; +} + +constexpr std::chrono::milliseconds operator"" _ms(unsigned long long v) +{ + return std::chrono::milliseconds{v}; +} + +constexpr std::chrono::microseconds operator"" _us(unsigned long long v) +{ + return std::chrono::microseconds{v}; +} + +constexpr std::chrono::nanoseconds operator"" _ns(unsigned long long v) +{ + return std::chrono::nanoseconds{v}; +} + +} // inline namespace chrono_literals +} // inline namespace literals +} // namespace dsn diff --git a/include/dsn/utility/error_code.h b/include/dsn/utility/error_code.h index 90a8a75f96..df738f107e 100644 --- a/include/dsn/utility/error_code.h +++ b/include/dsn/utility/error_code.h @@ -10,20 +10,19 @@ namespace dsn { class error_code { public: - error_code() { _internal_code = 0; } - error_code(const error_code &err) { _internal_code = err._internal_code; } explicit error_code(const char *name); - explicit error_code(int err) { _internal_code = err; } + + explicit constexpr error_code(int err) : _internal_code(err) {} + + constexpr error_code() = default; + const char *to_string() const; - error_code &operator=(const error_code &source) - { - _internal_code = source._internal_code; - return *this; - } - bool operator==(const error_code &r) { return _internal_code == r._internal_code; } - bool operator!=(const error_code &r) { return !(*this == r); } - operator int() const { return _internal_code; } + constexpr bool operator==(const error_code &r) { return _internal_code == r._internal_code; } + + constexpr bool operator!=(const error_code &r) { return !(*this == r); } + + constexpr operator int() const { return _internal_code; } #ifdef DSN_USE_THRIFT_SERIALIZATION uint32_t read(::apache::thrift::protocol::TProtocol *iprot); @@ -36,7 +35,7 @@ class error_code static error_code try_get(const std::string &name, error_code default_value); private: - int _internal_code; + int _internal_code{0}; }; #define DEFINE_ERR_CODE(x) __selectany const dsn::error_code x(#x); diff --git a/include/dsn/utility/errors.h b/include/dsn/utility/errors.h new file mode 100644 index 0000000000..4d6d36463e --- /dev/null +++ b/include/dsn/utility/errors.h @@ -0,0 +1,220 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Microsoft Corporation + * + * -=- Robust Distributed System Nucleus (rDSN) -=- + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include +#include +#include + +#include + +namespace dsn { + +// error_s gives a detailed description of the error tagged by error_code. +// For example: +// +// error_s open_file(std::string file_name) { +// if(file_name.empty()) { +// return error_s::make(ERR_INVALID_PARAMETERS, "file name should not be empty"); +// } +// return error_s::ok(); +// } +// +// error_s err = open_file(""); +// if (!err.is_ok()) { +// std::cerr << s.description() << std::endl; +// // print: "ERR_INVALID_PARAMETERS: file name should not be empty" +// } +// +class error_s +{ +public: + constexpr error_s() noexcept = default; + + ~error_s() = default; + + // copyable + error_s(const error_s &rhs) noexcept { copy(rhs); } + error_s &operator=(const error_s &rhs) noexcept + { + copy(rhs); + return (*this); + } + + // movable + error_s(error_s &&rhs) noexcept = default; + error_s &operator=(error_s &&) noexcept = default; + + static inline error_s make(error_code code, dsn::string_view reason) + { + return error_s(code, reason); + } + + static inline error_s make(error_code code) + { + // fast path + if (code == ERR_OK) { + return {}; + } + return make(code, ""); + } + + // Return a success status. + // This function is almost zero-cost since the returned object contains + // merely a null pointer. + static inline error_s ok() { return error_s(); } + + inline bool is_ok() const + { + if (_info) { + return _info->code == ERR_OK; + } + return true; + } + + std::string description() const + { + if (!_info) { + return ERR_OK.to_string(); + } + std::string code = _info->code.to_string(); + return _info->msg.empty() ? code : code + ": " + _info->msg; + } + + error_code code() const { return _info ? error_code(_info->code) : ERR_OK; } + + error_s &operator<<(const char str[]) + { + if (_info) { + _info->msg.append(str); + // It's fine for operator<< being applied to an OK Status. + } + return (*this); + } + + template + error_s &operator<<(T v) + { + if (_info) { + std::ostringstream oss; + oss << v; + (*this) << oss.str().c_str(); + } + return *this; + } + +public: + friend std::ostream &operator<<(std::ostream &os, const error_s &s) + { + return os << s.description(); + } + +private: + error_s(error_code code, dsn::string_view msg) noexcept : _info(new error_info(code, msg)) {} + + struct error_info + { + error_code code; + std::string msg; // TODO(wutao1): use raw char* to improve performance? + + error_info(error_code c, dsn::string_view s) : code(c), msg(s) {} + }; + + inline void copy(const error_s &rhs) + { + if (rhs._info == _info) { + return; + } + if (!rhs._info) { + _info.reset(); + } else if (!_info) { + _info = make_unique(rhs._info->code, rhs._info->msg); + } else { + _info->code = rhs._info->code; + _info->msg = rhs._info->msg; + } + } + +private: + std::unique_ptr _info; +}; + +// error_with is used to return an error or a value. +// For example: +// +// error_with result = ...; +// if (!s.is_ok()) { +// cerr << s.get_error().description()) << endl; +// } else { +// cerr << s.get_value() << endl; +// } +// +template +class error_with +{ +public: + // for ok case + error_with(const T &value) : _value(new T(value)) {} + error_with(T &&value) : _value(new T(std::move(value))) {} + + // for error case + error_with(error_s &&err) : _err(std::move(err)) { assert(!_err.is_ok()); } + error_with(const error_s &status) : _err(status) { assert(!_err.is_ok()); } + + const T &get_value() const + { + assert(_err.is_ok()); + return *_value; + } + + T &get_value() + { + assert(_err.is_ok()); + return *_value; + } + + const error_s &get_error() const { return _err; } + + error_s &get_error() { return _err; } + + bool is_ok() const { return _err.is_ok(); } + +private: + error_s _err; + std::unique_ptr _value; +}; + +} // namespace dsn + +#define FMT_ERR(ec, msg, args...) error_s::make(ec, fmt::format(msg, ##args)) + +#define RETURN_NOT_OK(s) \ + do { \ + const ::dsn::error_s &_s = (s); \ + if (!_s.is_ok()) \ + return _s; \ + } while (false); diff --git a/include/dsn/cpp/smart_pointers.h b/include/dsn/utility/smart_pointers.h similarity index 100% rename from include/dsn/cpp/smart_pointers.h rename to include/dsn/utility/smart_pointers.h diff --git a/include/dsn/utility/string_view.h b/include/dsn/utility/string_view.h new file mode 100644 index 0000000000..af60df5522 --- /dev/null +++ b/include/dsn/utility/string_view.h @@ -0,0 +1,372 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: string_view.h +// ----------------------------------------------------------------------------- +// +// This file contains the definition of the `dsn::string_view` class. A +// `string_view` points to a contiguous span of characters, often part or all of +// another `std::string`, double-quoted std::string literal, character array, or even +// another `string_view`. +// +// This `dsn::string_view` abstraction is designed to be a drop-in +// replacement for the C++17 `std::string_view` abstraction. +// +// --- Update(wutao1) --- +// +// This file is copied from abseil, though in order to maintain minimum +// dependencies, abseil is not an requirement. The dsn::string_view consists of only +// a subset of functions that std::string_view and absl::string_view provide, so that +// we can keep this module lightweight, but reducing the generality. +// +// dsn::string_view also supports view of dsn::blob, which can also function as a constant +// view. However, dsn::blob is not designed to be as lightweight as dsn::string_view +// since it requires at least one atomic operation to copy the internal std::shared_ptr. +// So in most cases where data is immutable, using dsn::string_view over dsn::blob will +// be a more proper choice. + +#pragma once + +#include +#include +#include +#include + +namespace dsn { + +// dsn::string_view +// +// A `string_view` provides a lightweight view into the std::string data provided by +// a `std::string`, double-quoted std::string literal, character array, or even +// another `string_view`. A `string_view` does *not* own the std::string to which it +// points, and that data cannot be modified through the view. +// +// You can use `string_view` as a function or method parameter anywhere a +// parameter can receive a double-quoted std::string literal, `const char*`, +// `std::string`, or another `absl::string_view` argument with no need to copy +// the std::string data. Systematic use of `string_view` within function arguments +// reduces data copies and `strlen()` calls. +// +// Because of its small size, prefer passing `string_view` by value: +// +// void MyFunction(dsn::string_view arg); +// +// If circumstances require, you may also pass one by const reference: +// +// void MyFunction(const dsn::string_view& arg); // not preferred +// +// Passing by value generates slightly smaller code for many architectures. +// +// In either case, the source data of the `string_view` must outlive the +// `string_view` itself. +// +// A `string_view` is also suitable for local variables if you know that the +// lifetime of the underlying object is longer than the lifetime of your +// `string_view` variable. However, beware of binding a `string_view` to a +// temporary value: +// +// // BAD use of string_view: lifetime problem +// dsn::string_view sv = obj.ReturnAString(); +// +// // GOOD use of string_view: str outlives sv +// std::string str = obj.ReturnAString(); +// dsn::string_view sv = str; +// +// Due to lifetime issues, a `string_view` is sometimes a poor choice for a +// return value and usually a poor choice for a data member. If you do use a +// `string_view` this way, it is your responsibility to ensure that the object +// pointed to by the `string_view` outlives the `string_view`. +// +// A `string_view` may represent a whole std::string or just part of a std::string. For +// example, when splitting a std::string, `std::vector` is a +// natural data type for the output. +// +// +// When constructed from a source which is nul-terminated, the `string_view` +// itself will not include the nul-terminator unless a specific size (including +// the nul) is passed to the constructor. As a result, common idioms that work +// on nul-terminated strings do not work on `string_view` objects. If you write +// code that scans a `string_view`, you must check its length rather than test +// for nul, for example. Note, however, that nuls may still be embedded within +// a `string_view` explicitly. +// +// You may create a null `string_view` in two ways: +// +// dsn::string_view sv(); +// dsn::string_view sv(nullptr, 0); +// +// For the above, `sv.data() == nullptr`, `sv.length() == 0`, and +// `sv.empty() == true`. Also, if you create a `string_view` with a non-null +// pointer then `sv.data() != nullptr`. Thus, you can use `string_view()` to +// signal an undefined value that is different from other `string_view` values +// in a similar fashion to how `const char* p1 = nullptr;` is different from +// `const char* p2 = "";`. However, in practice, it is not recommended to rely +// on this behavior. +// +// Be careful not to confuse a null `string_view` with an empty one. A null +// `string_view` is an empty `string_view`, but some empty `string_view`s are +// not null. Prefer checking for emptiness over checking for null. +// +// There are many ways to create an empty string_view: +// +// const char* nullcp = nullptr; +// // string_view.size() will return 0 in all cases. +// dsn::string_view(); +// dsn::string_view(nullcp, 0); +// dsn::string_view(""); +// dsn::string_view("", 0); +// dsn::string_view("abcdef", 0); +// dsn::string_view("abcdef" + 6, 0); +// +// All empty `string_view` objects whether null or not, are equal: +// +// dsn::string_view() == dsn::string_view("", 0) +// dsn::string_view(nullptr, 0) == dsn::string_view("abcdef"+6, 0) +class string_view +{ +public: + using traits_type = std::char_traits; + using value_type = char; + using pointer = char *; + using const_pointer = const char *; + using reference = char &; + using const_reference = const char &; + using const_iterator = const char *; + using iterator = const_iterator; + using const_reverse_iterator = std::reverse_iterator; + using reverse_iterator = const_reverse_iterator; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + + // Null `string_view` constructor + constexpr string_view() noexcept : ptr_(nullptr), length_(0) {} + + // Implicit constructors + + template + string_view( // NOLINT(runtime/explicit) + const std::basic_string, Allocator> &str) noexcept + : ptr_(str.data()), length_(str.size()) + { + } + + string_view(const blob &buf) noexcept // NOLINT(runtime/explicit) + : ptr_(buf.data()), + length_(buf.length()) + { + } + + constexpr string_view(const char *str) // NOLINT(runtime/explicit) + : ptr_(str), + length_(str == nullptr ? 0 : traits_type::length(str)) + { + } + + // Implicit constructor of a `string_view` from a `const char*` and length. + constexpr string_view(const char *data, size_type len) : ptr_(data), length_(len) {} + + // NOTE: Harmlessly omitted to work around gdb bug. + // constexpr string_view(const string_view&) noexcept = default; + // string_view& operator=(const string_view&) noexcept = default; + + // Iterators + + // string_view::begin() + // + // Returns an iterator pointing to the first character at the beginning of the + // `string_view`, or `end()` if the `string_view` is empty. + constexpr const_iterator begin() const noexcept { return ptr_; } + + // string_view::end() + // + // Returns an iterator pointing just beyond the last character at the end of + // the `string_view`. This iterator acts as a placeholder; attempting to + // access it results in undefined behavior. + constexpr const_iterator end() const noexcept { return ptr_ + length_; } + + // string_view::cbegin() + // + // Returns a const iterator pointing to the first character at the beginning + // of the `string_view`, or `end()` if the `string_view` is empty. + constexpr const_iterator cbegin() const noexcept { return begin(); } + + // string_view::cend() + // + // Returns a const iterator pointing just beyond the last character at the end + // of the `string_view`. This pointer acts as a placeholder; attempting to + // access its element results in undefined behavior. + constexpr const_iterator cend() const noexcept { return end(); } + + // string_view::rbegin() + // + // Returns a reverse iterator pointing to the last character at the end of the + // `string_view`, or `rend()` if the `string_view` is empty. + const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } + + // string_view::rend() + // + // Returns a reverse iterator pointing just before the first character at the + // beginning of the `string_view`. This pointer acts as a placeholder; + // attempting to access its element results in undefined behavior. + const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } + + // string_view::crbegin() + // + // Returns a const reverse iterator pointing to the last character at the end + // of the `string_view`, or `crend()` if the `string_view` is empty. + const_reverse_iterator crbegin() const noexcept { return rbegin(); } + + // string_view::crend() + // + // Returns a const reverse iterator pointing just before the first character + // at the beginning of the `string_view`. This pointer acts as a placeholder; + // attempting to access its element results in undefined behavior. + const_reverse_iterator crend() const noexcept { return rend(); } + + // Capacity Utilities + + // string_view::size() + // + // Returns the number of characters in the `string_view`. + constexpr size_type size() const noexcept { return length_; } + + // string_view::length() + // + // Returns the number of characters in the `string_view`. Alias for `size()`. + constexpr size_type length() const noexcept { return size(); } + + // string_view::empty() + // + // Checks if the `string_view` is empty (refers to no characters). + constexpr bool empty() const noexcept { return length_ == 0; } + + // std::string:view::operator[] + // + // Returns the ith element of an `string_view` using the array operator. + // Note that this operator does not perform any bounds checking. + constexpr const_reference operator[](size_type i) const { return ptr_[i]; } + + // string_view::front() + // + // Returns the first element of a `string_view`. + constexpr const_reference front() const { return ptr_[0]; } + + // string_view::back() + // + // Returns the last element of a `string_view`. + constexpr const_reference back() const { return ptr_[size() - 1]; } + + // string_view::data() + // + // Returns a pointer to the underlying character array (which is of course + // stored elsewhere). Note that `string_view::data()` may contain embedded nul + // characters, but the returned buffer may or may not be nul-terminated; + // therefore, do not pass `data()` to a routine that expects a nul-terminated + // std::string. + constexpr const_pointer data() const noexcept { return ptr_; } + + // Modifiers + + // string_view::remove_prefix() + // + // Removes the first `n` characters from the `string_view`. Note that the + // underlying std::string is not changed, only the view. + void remove_prefix(size_type n) + { + assert(n <= length_); + ptr_ += n; + length_ -= n; + } + + // string_view::remove_suffix() + // + // Removes the last `n` characters from the `string_view`. Note that the + // underlying std::string is not changed, only the view. + void remove_suffix(size_type n) + { + assert(n <= length_); + length_ -= n; + } + + // string_view::swap() + // + // Swaps this `string_view` with another `string_view`. + void swap(string_view &s) noexcept + { + auto t = *this; + *this = s; + s = t; + } + + // Explicit conversion operators + + // Converts to `std::basic_string`. + template + explicit operator std::basic_string() const + { + if (!data()) + return {}; + return std::basic_string(data(), size()); + } + + // string_view::compare() + // + // Performs a lexicographical comparison between the `string_view` and + // another `dsn::string_view), returning -1 if `this` is less than, 0 if + // `this` is equal to, and 1 if `this` is greater than the passed std::string + // view. Note that in the case of data equality, a further comparison is made + // on the respective sizes of the two `string_view`s to determine which is + // smaller, equal, or greater. + int compare(string_view x) const noexcept + { + auto min_length = std::min(length_, x.length_); + if (min_length > 0) { + int r = memcmp(ptr_, x.ptr_, min_length); + if (r < 0) + return -1; + if (r > 0) + return 1; + } + if (length_ < x.length_) + return -1; + if (length_ > x.length_) + return 1; + return 0; + } + +private: + const char *ptr_; + size_type length_; +}; + +// This large function is defined inline so that in a fairly common case where +// one of the arguments is a literal, the compiler can elide a lot of the +// following comparisons. +inline bool operator==(string_view x, string_view y) noexcept +{ + auto len = x.size(); + if (len != y.size()) { + return false; + } + return x.data() == y.data() || len <= 0 || memcmp(x.data(), y.data(), len) == 0; +} + +inline bool operator!=(string_view x, string_view y) noexcept { return !(x == y); } + +// IO Insertion Operator +std::ostream &operator<<(std::ostream &o, string_view piece); + +} // namespace dsn diff --git a/src/core/tests/smart_pointers_test.cpp b/src/core/tests/smart_pointers_test.cpp index d572a89fa6..518ee19ee1 100644 --- a/src/core/tests/smart_pointers_test.cpp +++ b/src/core/tests/smart_pointers_test.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include diff --git a/src/core/tests/string_view_test.cpp b/src/core/tests/string_view_test.cpp new file mode 100644 index 0000000000..43e556eb5c --- /dev/null +++ b/src/core/tests/string_view_test.cpp @@ -0,0 +1,268 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +namespace { + +TEST(StringViewTest, Ctor) +{ + { + // Null. + dsn::string_view s10; + EXPECT_TRUE(s10.data() == nullptr); + EXPECT_EQ(0, s10.length()); + } + + { + // const char* without length. + const char *hello = "hello"; + dsn::string_view s20(hello); + EXPECT_TRUE(s20.data() == hello); + EXPECT_EQ(5, s20.length()); + + // const char* with length. + dsn::string_view s21(hello, 4); + EXPECT_TRUE(s21.data() == hello); + EXPECT_EQ(4, s21.length()); + + // Not recommended, but valid C++ + dsn::string_view s22(hello, 6); + EXPECT_TRUE(s22.data() == hello); + EXPECT_EQ(6, s22.length()); + } + + { + // std::string. + std::string hola = "hola"; + dsn::string_view s30(hola); + EXPECT_TRUE(s30.data() == hola.data()); + EXPECT_EQ(4, s30.length()); + + // std::string with embedded '\0'. + hola.push_back('\0'); + hola.append("h2"); + hola.push_back('\0'); + dsn::string_view s31(hola); + EXPECT_TRUE(s31.data() == hola.data()); + EXPECT_EQ(8, s31.length()); + } +} + +TEST(StringViewTest, Swap) +{ + dsn::string_view a("a"); + dsn::string_view b("bbb"); + EXPECT_TRUE(noexcept(a.swap(b))); + a.swap(b); + EXPECT_EQ(a, "bbb"); + EXPECT_EQ(b, "a"); + a.swap(b); + EXPECT_EQ(a, "a"); + EXPECT_EQ(b, "bbb"); +} + +#define EXPECT_COMPARE_TRUE(op, x, y) \ + EXPECT_TRUE(dsn::string_view((x)) op dsn::string_view((y))); \ + EXPECT_TRUE(dsn::string_view((x)).compare(dsn::string_view((y))) op 0) + +#define EXPECT_COMPARE_FALSE(op, x, y) \ + EXPECT_FALSE(dsn::string_view((x)) op dsn::string_view((y))); \ + EXPECT_FALSE(dsn::string_view((x)).compare(dsn::string_view((y))) op 0) + +TEST(StringViewTest, ComparisonOperators) +{ + EXPECT_COMPARE_FALSE(==, "a", ""); + EXPECT_COMPARE_FALSE(==, "", "a"); + EXPECT_COMPARE_FALSE(==, "a", "b"); + EXPECT_COMPARE_FALSE(==, "a", "aa"); + EXPECT_COMPARE_FALSE(==, "aa", "a"); + + EXPECT_COMPARE_TRUE(==, "", ""); + EXPECT_COMPARE_TRUE(==, "", dsn::string_view()); + EXPECT_COMPARE_TRUE(==, dsn::string_view(), ""); + EXPECT_COMPARE_TRUE(==, "a", "a"); + EXPECT_COMPARE_TRUE(==, "aa", "aa"); + + EXPECT_COMPARE_FALSE(!=, "", ""); + EXPECT_COMPARE_FALSE(!=, "a", "a"); + EXPECT_COMPARE_FALSE(!=, "aa", "aa"); + + EXPECT_COMPARE_TRUE(!=, "a", ""); + EXPECT_COMPARE_TRUE(!=, "", "a"); + EXPECT_COMPARE_TRUE(!=, "a", "b"); + EXPECT_COMPARE_TRUE(!=, "a", "aa"); + EXPECT_COMPARE_TRUE(!=, "aa", "a"); +} + +TEST(StringViewTest, STL1) +{ + const dsn::string_view a("abcdefghijklmnopqrstuvwxyz"); + const dsn::string_view b("abc"); + const dsn::string_view c("xyz"); + const dsn::string_view d("foobar"); + const dsn::string_view e; + std::string temp("123"); + temp += '\0'; + temp += "456"; + const dsn::string_view f(temp); + + EXPECT_EQ(a[6], 'g'); + EXPECT_EQ(b[0], 'a'); + EXPECT_EQ(c[2], 'z'); + EXPECT_EQ(f[3], '\0'); + EXPECT_EQ(f[5], '5'); + + EXPECT_EQ(*d.data(), 'f'); + EXPECT_EQ(d.data()[5], 'r'); + EXPECT_TRUE(e.data() == nullptr); + + EXPECT_EQ(*a.begin(), 'a'); + EXPECT_EQ(*(b.begin() + 2), 'c'); + EXPECT_EQ(*(c.end() - 1), 'z'); + + EXPECT_EQ(*a.rbegin(), 'z'); + EXPECT_EQ(*(b.rbegin() + 2), 'a'); + EXPECT_EQ(*(c.rend() - 1), 'x'); + EXPECT_TRUE(a.rbegin() + 26 == a.rend()); + + EXPECT_EQ(a.size(), 26); + EXPECT_EQ(b.size(), 3); + EXPECT_EQ(c.size(), 3); + EXPECT_EQ(d.size(), 6); + EXPECT_EQ(e.size(), 0); + EXPECT_EQ(f.size(), 7); + + EXPECT_TRUE(!d.empty()); + EXPECT_TRUE(d.begin() != d.end()); + EXPECT_TRUE(d.begin() + 6 == d.end()); + + EXPECT_TRUE(e.empty()); + EXPECT_TRUE(e.begin() == e.end()); +} + +TEST(StringViewTest, Remove) +{ + dsn::string_view a("foobar"); + std::string s1("123"); + s1 += '\0'; + s1 += "456"; + dsn::string_view b(s1); + dsn::string_view e; + std::string s2; + + // remove_prefix + dsn::string_view c(a); + c.remove_prefix(3); + EXPECT_EQ(c, "bar"); + c = a; + c.remove_prefix(0); + EXPECT_EQ(c, a); + c.remove_prefix(c.size()); + EXPECT_EQ(c, e); + + // remove_suffix + c = a; + c.remove_suffix(3); + EXPECT_EQ(c, "foo"); + c = a; + c.remove_suffix(0); + EXPECT_EQ(c, a); + c.remove_suffix(c.size()); + EXPECT_EQ(c, e); +} + +TEST(StringViewTest, Set) +{ + dsn::string_view a("foobar"); + dsn::string_view empty; + dsn::string_view b; + + // set + b = dsn::string_view("foobar", 6); + EXPECT_EQ(b, a); + b = dsn::string_view("foobar", 0); + EXPECT_EQ(b, empty); + b = dsn::string_view("foobar", 7); + EXPECT_NE(b, a); + + b = dsn::string_view("foobar"); + EXPECT_EQ(b, a); +} + +TEST(StringViewTest, FrontBack) +{ + static const char arr[] = "abcd"; + const dsn::string_view csp(arr, 4); + EXPECT_EQ(&arr[0], &csp.front()); + EXPECT_EQ(&arr[3], &csp.back()); +} + +TEST(StringViewTest, FrontBackSingleChar) +{ + static const char c = 'a'; + const dsn::string_view csp(&c, 1); + EXPECT_EQ(&c, &csp.front()); + EXPECT_EQ(&c, &csp.back()); +} + +TEST(StringViewTest, NULLInput) +{ + dsn::string_view s; + EXPECT_EQ(s.data(), nullptr); + EXPECT_EQ(s.size(), 0); + + s = dsn::string_view(nullptr); + EXPECT_EQ(s.data(), nullptr); + EXPECT_EQ(s.size(), 0); + + EXPECT_EQ("", std::string(s)); +} + +TEST(StringViewTest, ExplicitConversionOperator) +{ + dsn::string_view sp = "hi"; + EXPECT_EQ(sp, std::string(sp)); +} + +TEST(StringViewTest, Noexcept) +{ + EXPECT_TRUE((std::is_nothrow_constructible::value)); + EXPECT_TRUE((std::is_nothrow_constructible::value)); + EXPECT_TRUE(std::is_nothrow_constructible::value); + constexpr dsn::string_view sp; + EXPECT_TRUE(noexcept(sp.begin())); + EXPECT_TRUE(noexcept(sp.end())); + EXPECT_TRUE(noexcept(sp.cbegin())); + EXPECT_TRUE(noexcept(sp.cend())); + EXPECT_TRUE(noexcept(sp.rbegin())); + EXPECT_TRUE(noexcept(sp.rend())); + EXPECT_TRUE(noexcept(sp.crbegin())); + EXPECT_TRUE(noexcept(sp.crend())); + EXPECT_TRUE(noexcept(sp.size())); + EXPECT_TRUE(noexcept(sp.length())); + EXPECT_TRUE(noexcept(sp.empty())); + EXPECT_TRUE(noexcept(sp.data())); + EXPECT_TRUE(noexcept(sp.compare(sp))); +} + +TEST(StringViewTest, HeterogenousStringViewEquals) +{ + EXPECT_EQ(dsn::string_view("hello"), std::string("hello")); + EXPECT_EQ("hello", dsn::string_view("hello")); +} + +} // namespace dsn diff --git a/src/dist/replication/lib/CMakeLists.txt b/src/dist/replication/lib/CMakeLists.txt index c0fa8978e0..f557bf0c9f 100644 --- a/src/dist/replication/lib/CMakeLists.txt +++ b/src/dist/replication/lib/CMakeLists.txt @@ -15,6 +15,7 @@ set(MY_PROJ_LIBS dsn.replication.clientlib dsn.failure_detector dsn.failure_detector.multimaster + fmt ) set(MY_PROJ_LIB_PATH "") diff --git a/src/dist/replication/lib/replication_app_base.cpp b/src/dist/replication/lib/replication_app_base.cpp index 69f15de9c3..6fe7d4a15a 100644 --- a/src/dist/replication/lib/replication_app_base.cpp +++ b/src/dist/replication/lib/replication_app_base.cpp @@ -40,7 +40,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/thirdparty/build-thirdparty.sh b/thirdparty/build-thirdparty.sh index 21c8beedb1..b8ce4a5b1f 100755 --- a/thirdparty/build-thirdparty.sh +++ b/thirdparty/build-thirdparty.sh @@ -163,6 +163,18 @@ else echo "skip build libevent" fi +# build fmtlib +if [ ! -d $TP_OUTPUT/include/fmt ]; then + cd $TP_SRC/fmt-4.0.0 + mkdir -p build && cd build + cmake .. -DCMAKE_INSTALL_PREFIX=$TP_OUTPUT -DFMT_TEST=false + make -j8 && make install + cd $TP_DIR + exit_if_fail "fmtlib" $? +else + echo "skip build fmtlib" +fi + # build poco if [ ! -d $TP_OUTPUT/include/Poco ]; then mkdir -p $TP_BUILD/poco-1.7.8-release diff --git a/thirdparty/download-thirdparty.sh b/thirdparty/download-thirdparty.sh index 9ea4f2c5f6..215bed8280 100755 --- a/thirdparty/download-thirdparty.sh +++ b/thirdparty/download-thirdparty.sh @@ -106,4 +106,8 @@ else echo "fds has already downloaded, skip it" fi +# fmtlib +check_and_download "fmt-4.0.0.tar.gz" "https://codeload.github.com/fmtlib/fmt/tar.gz/4.0.0" +exit_if_fail $? + cd $TP_DIR