diff options
author | Andy Schwerin <schwerin@mongodb.com> | 2016-05-04 19:20:14 -0400 |
---|---|---|
committer | Andy Schwerin <schwerin@mongodb.com> | 2016-05-17 11:11:57 -0400 |
commit | 11e82fe19c81bd1a5d350a219c3a000e959b6dd6 (patch) | |
tree | cd7798a660b4c8791caad44d0b25e27bceb03938 /src/mongo/util/duration.h | |
parent | d22193f28e3113d42b4b7248b25f476dc75a7da7 (diff) | |
download | mongo-11e82fe19c81bd1a5d350a219c3a000e959b6dd6.tar.gz |
Introduce a replacement for std::chrono::duration without undefined behavior.
Diffstat (limited to 'src/mongo/util/duration.h')
-rw-r--r-- | src/mongo/util/duration.h | 410 |
1 files changed, 410 insertions, 0 deletions
diff --git a/src/mongo/util/duration.h b/src/mongo/util/duration.h new file mode 100644 index 00000000000..ccde7fd57a3 --- /dev/null +++ b/src/mongo/util/duration.h @@ -0,0 +1,410 @@ +/* Copyright 2016 MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include <cstdint> +#include <iosfwd> +#include <limits> +#include <ratio> + +#include "mongo/platform/overflow_arithmetic.h" +#include "mongo/stdx/type_traits.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + +template <typename Allocator> +class StringBuilderImpl; + +namespace x { + +template <typename Period> +class Duration; + +using Nanoseconds = Duration<std::nano>; +using Microseconds = Duration<std::micro>; +using Milliseconds = Duration<std::milli>; +using Seconds = Duration<std::ratio<1>>; +using Minutes = Duration<std::ratio<60>>; +using Hours = Duration<std::ratio<3600>>; + +// +// Streaming output operators for common duration types. Writes the numerical value followed by +// an abbreviated unit, without a space. +// +// E.g., std::cout << Minutes{5} << std::endl; should produce the following: +// 5min +// + +std::ostream& operator<<(std::ostream& os, Nanoseconds ns); +std::ostream& operator<<(std::ostream& os, Microseconds us); +std::ostream& operator<<(std::ostream& os, Milliseconds ms); +std::ostream& operator<<(std::ostream& os, Seconds s); +std::ostream& operator<<(std::ostream& os, Minutes m); +std::ostream& operator<<(std::ostream& os, Hours h); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Nanoseconds ns); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Microseconds us); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Milliseconds ms); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Seconds s); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Minutes m); + +template <typename Allocator> +StringBuilderImpl<Allocator>& operator<<(StringBuilderImpl<Allocator>& os, Hours h); + + +template <typename Duration1, typename Duration2> +using HigherPrecisionDuration = + typename std::conditional<!Duration1::template IsLowerPrecisionThan<Duration2>::value, + Duration1, + Duration2>::type; + +/** + * Casts from one Duration precision to another. + * + * May throw a UserException if "from" is of lower-precision type and is outside the range of the + * ToDuration. For example, Seconds::max() cannot be represented as a Milliseconds, and + * so attempting to cast that value to Milliseconds will throw an exception. + */ +template <typename ToDuration, typename FromPeriod> +ToDuration durationCast(const Duration<FromPeriod>& from) { + using FromOverTo = std::ratio_divide<FromPeriod, typename ToDuration::period>; + if (ToDuration::template isHigherPrecisionThan<Duration<FromPeriod>>()) { + typename ToDuration::rep toCount; + uassert(ErrorCodes::DurationOverflow, + "Overflow casting from a lower-precision duration to a higher-precision duration", + !mongoSignedMultiplyOverflow64(from.count(), FromOverTo::num, &toCount)); + return ToDuration{toCount}; + } + return ToDuration{from.count() / FromOverTo::den}; +} + +/** + * Type representing a duration using a 64-bit counter. + * + * The Period template argument is a std::ratio describing the units of the duration type. + * + * This type's behavior is similar to std::chrono::duration, but instead of undefined behavior on + * overflows and other conversions, throws exceptions. + */ +template <typename Period> +class Duration { +public: + static_assert(Period::num > 0, "Duration::period's numerator must be positive"); + static_assert(Period::den > 0, "Duration::period's denominator must be positive"); + + using rep = int64_t; + using period = Period; + + /** + * Type with static bool "value" set to true if this Duration type is higher precision than + * OtherDuration. That is, if OtherDuration::period > period. + */ + template <typename OtherDuration> + struct IsHigherPrecisionThan { + using OtherOverThis = std::ratio_divide<typename OtherDuration::period, period>; + static_assert(OtherOverThis::den == 1 || OtherOverThis::num == 1, + "Mongo duration types are only compatible with each other when one's period " + "is an even multiple of the other's."); + static constexpr bool value = OtherOverThis::den == 1 && OtherOverThis::num != 1; + }; + + /** + * Type with static bool "value" set to true if this Duration type is lower precision than + * OtherDuration. That is, if OtherDuration::period > period. + */ + template <typename OtherDuration> + struct IsLowerPrecisionThan { + using OtherOverThis = std::ratio_divide<typename OtherDuration::period, period>; + static_assert(OtherOverThis::den == 1 || OtherOverThis::num == 1, + "Mongo duration types are only compatible with each other when one's period " + "is an even multiple of the other's."); + static constexpr bool value = OtherOverThis::num == 1 && OtherOverThis::den != 1; + }; + + /** + * Function that returns true if period > OtherDuration::period. + */ + template <typename OtherDuration> + constexpr static bool isHigherPrecisionThan() { + return IsHigherPrecisionThan<OtherDuration>::value; + } + + /** + * Function that returns true if period < OtherDuration::period. + */ + template <typename OtherDuration> + constexpr static bool isLowerPrecisionThan() { + return IsLowerPrecisionThan<OtherDuration>::value; + } + + static constexpr Duration zero() { + return Duration{}; + } + + static constexpr Duration min() { + return Duration{std::numeric_limits<rep>::min()}; + } + + static constexpr Duration max() { + return Duration{std::numeric_limits<rep>::max()}; + } + + /** + * Constructs the zero duration. + */ + constexpr Duration() = default; + + /** + * Constructs a duration representing "r" periods. + */ + template <typename Rep2> + constexpr explicit Duration(const Rep2& r) + : _count(r) { + static_assert(std::is_integral<Rep2>::value && std::is_signed<Rep2>::value, + "Durations must be constructed from values of signed integral type"); + } + + /** + * Constructs a higher-precision duration from a lower-precision one, as by durationCast. + * + * Throws a UserException if "from" is out of the range of this duration type. + * + * It is a compilation error to attempt a conversion from higher-precision to lower-precision by + * this constructor. + */ + template <typename FromPeriod> + /*implicit*/ Duration(const Duration<FromPeriod>& from) : Duration(durationCast<Duration>(from)) { + static_assert(!isLowerPrecisionThan<Duration<FromPeriod>>(), + "Use duration_cast to convert from higher precision Duration types to lower " + "precision ones"); + } + + /** + * Returns the number of periods represented by this duration. + */ + constexpr rep count() const { + return _count; + } + + /** + * Compares this duration to another duration of the same type. + * + * Returns 1, -1 or 0 depending on whether this duration is greater than, less than or equal to + * the other duration, respectively. + */ + constexpr int compare(const Duration& other) const { + return (count() > other.count()) ? 1 : (count() < other.count()) ? -1 : 0; + } + + /** + * Compares this duration to a lower-precision duration, "other". + */ + template <typename OtherPeriod> + int compare(const Duration<OtherPeriod>& other) const { + if (isLowerPrecisionThan<Duration<OtherPeriod>>()) { + return -other.compare(*this); + } + using OtherOverThis = std::ratio_divide<OtherPeriod, period>; + rep otherCount; + if (mongoSignedMultiplyOverflow64(other.count(), OtherOverThis::num, &otherCount)) { + return other.count() < 0 ? 1 : -1; + } + if (count() < otherCount) { + return -1; + } + if (count() > otherCount) { + return 1; + } + return 0; + } + + constexpr Duration operator+() const { + return *this; + } + + Duration operator-() const { + uassert(ErrorCodes::DurationOverflow, "Cannot negate the minimum duration", *this != min()); + return Duration(-count()); + } + + // + // In-place arithmetic operators + // + + Duration& operator++() { + return (*this) += Duration{1}; + } + + Duration operator++(int) { + auto result = *this; + *this += Duration{1}; + return result; + } + + Duration operator--() { + return (*this) -= Duration{1}; + } + + Duration operator--(int) { + auto result = *this; + *this -= Duration{1}; + return result; + } + + Duration& operator+=(const Duration& other) { + uassert(ErrorCodes::DurationOverflow, + str::stream() << "Overflow while adding " << other << " to " << *this, + !mongoSignedAddOverflow64(count(), other.count(), &_count)); + return *this; + } + + Duration& operator-=(const Duration& other) { + uassert(ErrorCodes::DurationOverflow, + str::stream() << "Overflow while subtracting " << other << " from " << *this, + !mongoSignedSubtractOverflow64(count(), other.count(), &_count)); + return *this; + } + + template <typename Rep2> + Duration& operator*=(const Rep2& scale) { + static_assert(std::is_integral<Rep2>::value && std::is_signed<Rep2>::value, + "Durations may only be multiplied by values of signed integral type"); + uassert(ErrorCodes::DurationOverflow, + str::stream() << "Overflow while multiplying " << *this << " by " << scale, + !mongoSignedMultiplyOverflow64(count(), scale, &_count)); + return *this; + } + + template <typename Rep2> + Duration& operator/=(const Rep2& scale) { + static_assert(std::is_integral<Rep2>::value && std::is_signed<Rep2>::value, + "Durations may only be divided by values of signed integral type"); + uassert(ErrorCodes::DurationOverflow, + str::stream() << "Overflow while dividing " << *this << " by -1", + (count() != min().count() || scale != -1)); + _count /= scale; + return *this; + } + +private: + rep _count = {}; +}; + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator==(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) == 0; +} + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator!=(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) != 0; +} + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator<(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) < 0; +} + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator<=(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) <= 0; +} + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator>(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) > 0; +} + +template <typename LhsPeriod, typename RhsPeriod> +constexpr bool operator>=(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + return lhs.compare(rhs) >= 0; +} + +/** + * Returns the sum of two durations, "lhs" and "rhs". + */ +template < + typename LhsPeriod, + typename RhsPeriod, + typename ReturnDuration = HigherPrecisionDuration<Duration<LhsPeriod>, Duration<RhsPeriod>>> +ReturnDuration operator+(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + ReturnDuration result = lhs; + result += rhs; + return result; +} + +/** + * Returns the result of subtracting "rhs" from "lhs". + */ +template < + typename LhsPeriod, + typename RhsPeriod, + typename ReturnDuration = HigherPrecisionDuration<Duration<LhsPeriod>, Duration<RhsPeriod>>> +ReturnDuration operator-(const Duration<LhsPeriod>& lhs, const Duration<RhsPeriod>& rhs) { + ReturnDuration result = lhs; + result -= rhs; + return result; +} + +/** + * Returns the product of a duration "d" and a unitless integer, "scale". + */ +template <typename Period, typename Rep2> +Duration<Period> operator*(Duration<Period> d, const Rep2& scale) { + d *= scale; + return d; +} + +template <typename Period, typename Rep2> +Duration<Period> operator*(const Rep2& scale, Duration<Period> d) { + d *= scale; + return d; +} + +/** + * Returns duration "d" divided by unitless integer "scale". + */ +template <typename Period, typename Rep2> +Duration<Period> operator/(Duration<Period> d, const Rep2& scale) { + d /= scale; + return d; +} + +} // namespace x +} // namespace mongo |