summaryrefslogtreecommitdiff
path: root/src/mongo/util/duration.h
diff options
context:
space:
mode:
authorAndy Schwerin <schwerin@mongodb.com>2016-05-04 19:20:14 -0400
committerAndy Schwerin <schwerin@mongodb.com>2016-05-17 11:11:57 -0400
commit11e82fe19c81bd1a5d350a219c3a000e959b6dd6 (patch)
treecd7798a660b4c8791caad44d0b25e27bceb03938 /src/mongo/util/duration.h
parentd22193f28e3113d42b4b7248b25f476dc75a7da7 (diff)
downloadmongo-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.h410
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