summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Derek Mauro <dmauro@google.com>2023-02-09 19:45:05 -0800
committerGravatar Copybara-Service <copybara-worker@google.com>2023-02-09 19:45:48 -0800
commitfa4855403cdfb91b9c16f792041258ed819594c1 (patch)
tree49546e9be6ce6fafbc78e4120b8ad51de386fdf3
parent823b837839db2a754af37095c72f97ce6733d32b (diff)
Rewrite KernelTimeout to support both absolute and relative timeouts
APIs that take KernelTimeout as a parameter can now query if an absolute or relative timeout was requested. If the underlying API can only use one type of timeout, the code will do a reasonable conversion. The goal is to eventually enable the possibility of using wait times that are based on monotonic clocks that are safe against system clock steps. PiperOrigin-RevId: 508541507 Change-Id: Id08bf13515f3e1bfd78d88393cde98a6fd3ef72c
-rw-r--r--CMake/AbseilDll.cmake1
-rw-r--r--absl/synchronization/BUILD.bazel16
-rw-r--r--absl/synchronization/CMakeLists.txt18
-rw-r--r--absl/synchronization/internal/kernel_timeout.cc168
-rw-r--r--absl/synchronization/internal/kernel_timeout.h180
-rw-r--r--absl/synchronization/internal/kernel_timeout_test.cc278
6 files changed, 546 insertions, 115 deletions
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake
index 52a563cd..3c0f6901 100644
--- a/CMake/AbseilDll.cmake
+++ b/CMake/AbseilDll.cmake
@@ -365,6 +365,7 @@ set(ABSL_INTERNAL_DLL_FILES
"synchronization/internal/graphcycles.cc"
"synchronization/internal/graphcycles.h"
"synchronization/internal/kernel_timeout.h"
+ "synchronization/internal/kernel_timeout.cc"
"synchronization/internal/per_thread_sem.cc"
"synchronization/internal/per_thread_sem.h"
"synchronization/internal/thread_pool.h"
diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel
index ccaee796..9c89ac49 100644
--- a/absl/synchronization/BUILD.bazel
+++ b/absl/synchronization/BUILD.bazel
@@ -53,6 +53,7 @@ cc_library(
cc_library(
name = "kernel_timeout_internal",
+ srcs = ["internal/kernel_timeout.cc"],
hdrs = ["internal/kernel_timeout.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -60,12 +61,25 @@ cc_library(
"//absl/synchronization:__pkg__",
],
deps = [
- "//absl/base:core_headers",
+ "//absl/base:config",
"//absl/base:raw_logging_internal",
"//absl/time",
],
)
+cc_test(
+ name = "kernel_timeout_internal_test",
+ srcs = ["internal/kernel_timeout_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":kernel_timeout_internal",
+ "//absl/base:config",
+ "//absl/time",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
cc_library(
name = "synchronization",
srcs = [
diff --git a/absl/synchronization/CMakeLists.txt b/absl/synchronization/CMakeLists.txt
index f64653bb..5eb9b566 100644
--- a/absl/synchronization/CMakeLists.txt
+++ b/absl/synchronization/CMakeLists.txt
@@ -39,14 +39,30 @@ absl_cc_library(
kernel_timeout_internal
HDRS
"internal/kernel_timeout.h"
+ SRCS
+ "internal/kernel_timeout.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
- absl::core_headers
+ absl::config
absl::raw_logging_internal
absl::time
)
+absl_cc_test(
+ NAME
+ kernel_timeout_internal_test
+ SRCS
+ "internal/kernel_timeout_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::kernel_timeout_internal
+ absl::config
+ absl::time
+ GTest::gmock_main
+)
+
absl_cc_library(
NAME
synchronization
diff --git a/absl/synchronization/internal/kernel_timeout.cc b/absl/synchronization/internal/kernel_timeout.cc
new file mode 100644
index 00000000..4015bd0c
--- /dev/null
+++ b/absl/synchronization/internal/kernel_timeout.cc
@@ -0,0 +1,168 @@
+// Copyright 2023 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
+//
+// https://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 "absl/synchronization/internal/kernel_timeout.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <ctime>
+#include <limits>
+
+#include "absl/base/config.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr uint64_t KernelTimeout::kNoTimeout;
+constexpr int64_t KernelTimeout::kMaxNanos;
+#endif
+
+KernelTimeout::KernelTimeout(absl::Time t) {
+ // `absl::InfiniteFuture()` is a common "no timeout" value and cheaper to
+ // compare than convert.
+ if (t == absl::InfiniteFuture()) {
+ rep_ = kNoTimeout;
+ return;
+ }
+
+ int64_t unix_nanos = absl::ToUnixNanos(t);
+
+ // A timeout that lands before the unix epoch is converted to 0.
+ // In theory implementations should expire these timeouts immediately.
+ if (unix_nanos < 0) {
+ unix_nanos = 0;
+ }
+
+ // Values greater than or equal to kMaxNanos are converted to infinite.
+ if (unix_nanos >= kMaxNanos) {
+ rep_ = kNoTimeout;
+ return;
+ }
+
+ rep_ = static_cast<uint64_t>(unix_nanos) << 1;
+}
+
+KernelTimeout::KernelTimeout(absl::Duration d) {
+ // `absl::InfiniteDuration()` is a common "no timeout" value and cheaper to
+ // compare than convert.
+ if (d == absl::InfiniteDuration()) {
+ rep_ = kNoTimeout;
+ return;
+ }
+
+ int64_t nanos = absl::ToInt64Nanoseconds(d);
+
+ // Negative durations are normalized to 0.
+ // In theory implementations should expire these timeouts immediately.
+ if (nanos < 0) {
+ nanos = 0;
+ }
+
+ // Values greater than or equal to kMaxNanos are converted to infinite.
+ if (nanos >= kMaxNanos) {
+ rep_ = kNoTimeout;
+ return;
+ }
+
+ rep_ = (static_cast<uint64_t>(nanos) << 1) | uint64_t{1};
+}
+
+int64_t KernelTimeout::MakeAbsNanos() const {
+ if (!has_timeout()) {
+ return kMaxNanos;
+ }
+
+ int64_t nanos = RawNanos();
+
+ if (is_relative_timeout()) {
+ int64_t now = ToUnixNanos(absl::Now());
+ if (nanos > kMaxNanos - now) {
+ // Overflow.
+ nanos = kMaxNanos;
+ } else {
+ nanos += now;
+ }
+ } else if (nanos == 0) {
+ // Some callers have assumed that 0 means no timeout, so instead we return a
+ // time of 1 nanosecond after the epoch.
+ nanos = 1;
+ }
+
+ return nanos;
+}
+
+struct timespec KernelTimeout::MakeAbsTimespec() const {
+ return absl::ToTimespec(absl::Nanoseconds(MakeAbsNanos()));
+}
+
+struct timespec KernelTimeout::MakeRelativeTimespec() const {
+ if (!has_timeout()) {
+ return absl::ToTimespec(absl::Nanoseconds(kMaxNanos));
+ }
+ if (is_relative_timeout()) {
+ return absl::ToTimespec(absl::Nanoseconds(RawNanos()));
+ }
+
+ int64_t nanos = RawNanos();
+ int64_t now = ToUnixNanos(absl::Now());
+ if (now > nanos) {
+ // Convert past values to 0 to be safe.
+ nanos = 0;
+ } else {
+ nanos -= now;
+ }
+ return absl::ToTimespec(absl::Nanoseconds(nanos));
+}
+
+KernelTimeout::DWord KernelTimeout::InMillisecondsFromNow() const {
+ constexpr DWord kInfinite = std::numeric_limits<DWord>::max();
+
+ if (!has_timeout()) {
+ return kInfinite;
+ }
+
+ const int64_t nanos = RawNanos();
+ constexpr uint64_t kNanosInMillis = uint64_t{1000000};
+
+ if (is_relative_timeout()) {
+ uint64_t ms = static_cast<uint64_t>(nanos) / kNanosInMillis;
+ if (ms > static_cast<uint64_t>(kInfinite)) {
+ ms = static_cast<uint64_t>(kInfinite);
+ }
+ return static_cast<DWord>(ms);
+ }
+
+ int64_t now = ToUnixNanos(absl::Now());
+ if (nanos >= now) {
+ // Round up so that now + ms_from_now >= nanos.
+ constexpr uint64_t kMaxValueNanos =
+ std::numeric_limits<int64_t>::max() - kNanosInMillis + 1;
+ uint64_t ms_from_now =
+ (std::min(kMaxValueNanos, static_cast<uint64_t>(nanos - now)) +
+ kNanosInMillis - 1) /
+ kNanosInMillis;
+ if (ms_from_now > kInfinite) {
+ return kInfinite;
+ }
+ return static_cast<DWord>(ms_from_now);
+ }
+ return DWord{0};
+}
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/synchronization/internal/kernel_timeout.h b/absl/synchronization/internal/kernel_timeout.h
index f5c2c0ef..1f4d82cd 100644
--- a/absl/synchronization/internal/kernel_timeout.h
+++ b/absl/synchronization/internal/kernel_timeout.h
@@ -11,26 +11,16 @@
// 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.
-//
-
-// An optional absolute timeout, with nanosecond granularity,
-// compatible with absl::Time. Suitable for in-register
-// parameter-passing (e.g. syscalls.)
-// Constructible from a absl::Time (for a timeout to be respected) or {}
-// (for "no timeout".)
-// This is a private low-level API for use by a handful of low-level
-// components. Higher-level components should build APIs based on
-// absl::Time and absl::Duration.
#ifndef ABSL_SYNCHRONIZATION_INTERNAL_KERNEL_TIMEOUT_H_
#define ABSL_SYNCHRONIZATION_INTERNAL_KERNEL_TIMEOUT_H_
-#include <time.h>
-
#include <algorithm>
#include <cstdint>
+#include <ctime>
#include <limits>
+#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
@@ -41,56 +31,59 @@ namespace synchronization_internal {
class Waiter;
+// An optional timeout, with nanosecond granularity.
+//
+// This is a private low-level API for use by a handful of low-level
+// components. Higher-level components should build APIs based on
+// absl::Time and absl::Duration.
class KernelTimeout {
public:
- // A timeout that should expire at <t>. Any value, in the full
- // InfinitePast() to InfiniteFuture() range, is valid here and will be
- // respected.
- explicit KernelTimeout(absl::Time t) : ns_(MakeNs(t)) {}
- // No timeout.
- KernelTimeout() : ns_(0) {}
+ // Construct an absolute timeout that should expire at `t`.
+ explicit KernelTimeout(absl::Time t);
+
+ // Construct a relative timeout that should expire after `d`.
+ explicit KernelTimeout(absl::Duration d);
+
+ // Infinite timeout.
+ constexpr KernelTimeout() : rep_(kNoTimeout) {}
+
+ // A more explicit factory for those who prefer it.
+ // Equivalent to `KernelTimeout()`.
+ static constexpr KernelTimeout Never() { return KernelTimeout(); }
- // A more explicit factory for those who prefer it. Equivalent to {}.
- static KernelTimeout Never() { return {}; }
+ // Returns true if there is a timeout that will eventually expire.
+ // Returns false if the timeout is infinite.
+ bool has_timeout() const { return rep_ != kNoTimeout; }
- // We explicitly do not support other custom formats: timespec, int64_t nanos.
- // Unify on this and absl::Time, please.
+ // If `has_timeout()` is true, returns true if the timeout was provided as an
+ // `absl::Time`. The return value is undefined if `has_timeout()` is false
+ // because all indefinite timeouts are equivalent.
+ bool is_absolute_timeout() const { return (rep_ & 1) == 0; }
- bool has_timeout() const { return ns_ != 0; }
+ // If `has_timeout()` is true, returns true if the timeout was provided as an
+ // `absl::Duration`. The return value is undefined if `has_timeout()` is false
+ // because all indefinite timeouts are equivalent.
+ bool is_relative_timeout() const { return (rep_ & 1) == 1; }
- // Convert to parameter for sem_timedwait/futex/similar. Only for approved
- // users. Do not call if !has_timeout.
+ // Convert to `struct timespec` for interfaces that expect an absolute
+ // timeout. If !has_timeout() or is_relative_timeout(), attempts to convert to
+ // a reasonable absolute timeout, but callers should to test has_timeout() and
+ // is_relative_timeout() and prefer to use a more appropriate interface.
struct timespec MakeAbsTimespec() const;
- // Convert to unix epoch nanos. Do not call if !has_timeout.
+ // Convert to `struct timespec` for interfaces that expect a relative
+ // timeout. If !has_timeout() or is_absolute_timeout(), attempts to convert to
+ // a reasonable relative timeout, but callers should to test has_timeout() and
+ // is_absolute_timeout() and prefer to use a more appropriate interface.
+ struct timespec MakeRelativeTimespec() const;
+
+ // Convert to unix epoch nanos for interfaces that expect an absolute timeout
+ // in nanoseconds. If !has_timeout() or is_relative_timeout(), attempts to
+ // convert to a reasonable absolute timeout, but callers should to test
+ // has_timeout() and is_relative_timeout() and prefer to use a more
+ // appropriate interface.
int64_t MakeAbsNanos() const;
- private:
- // internal rep, not user visible: ns after unix epoch.
- // zero = no timeout.
- // Negative we treat as an unlikely (and certainly expired!) but valid
- // timeout.
- int64_t ns_;
-
- static int64_t MakeNs(absl::Time t) {
- // optimization--InfiniteFuture is common "no timeout" value
- // and cheaper to compare than convert.
- if (t == absl::InfiniteFuture()) return 0;
- int64_t x = ToUnixNanos(t);
-
- // A timeout that lands exactly on the epoch (x=0) needs to be respected,
- // so we alter it unnoticably to 1. Negative timeouts are in
- // theory supported, but handled poorly by the kernel (long
- // delays) so push them forward too; since all such times have
- // already passed, it's indistinguishable.
- if (x <= 0) x = 1;
- // A time larger than what can be represented to the kernel is treated
- // as no timeout.
- if (x == (std::numeric_limits<int64_t>::max)()) x = 0;
- return x;
- }
-
-#ifdef _WIN32
// Converts to milliseconds from now, or INFINITE when
// !has_timeout(). For use by SleepConditionVariableSRW on
// Windows. Callers should recognize that the return value is a
@@ -100,68 +93,29 @@ class KernelTimeout {
// so we define our own DWORD and INFINITE instead of getting them from
// <intsafe.h> and <WinBase.h>.
typedef unsigned long DWord; // NOLINT
- DWord InMillisecondsFromNow() const {
- constexpr DWord kInfinite = (std::numeric_limits<DWord>::max)();
- if (!has_timeout()) {
- return kInfinite;
- }
- // The use of absl::Now() to convert from absolute time to
- // relative time means that absl::Now() cannot use anything that
- // depends on KernelTimeout (for example, Mutex) on Windows.
- int64_t now = ToUnixNanos(absl::Now());
- if (ns_ >= now) {
- // Round up so that Now() + ms_from_now >= ns_.
- constexpr uint64_t max_nanos =
- (std::numeric_limits<int64_t>::max)() - 999999u;
- uint64_t ms_from_now =
- ((std::min)(max_nanos, static_cast<uint64_t>(ns_ - now)) + 999999u) /
- 1000000u;
- if (ms_from_now > kInfinite) {
- return kInfinite;
- }
- return static_cast<DWord>(ms_from_now);
- }
- return 0;
- }
-
- friend class Waiter;
-#endif
-};
+ DWord InMillisecondsFromNow() const;
-inline struct timespec KernelTimeout::MakeAbsTimespec() const {
- int64_t n = ns_;
- static const int64_t kNanosPerSecond = 1000 * 1000 * 1000;
- if (n == 0) {
- ABSL_RAW_LOG(
- ERROR, "Tried to create a timespec from a non-timeout; never do this.");
- // But we'll try to continue sanely. no-timeout ~= saturated timeout.
- n = (std::numeric_limits<int64_t>::max)();
- }
-
- // Kernel APIs validate timespecs as being at or after the epoch,
- // despite the kernel time type being signed. However, no one can
- // tell the difference between a timeout at or before the epoch (since
- // all such timeouts have expired!)
- if (n < 0) n = 0;
-
- struct timespec abstime;
- int64_t seconds = (std::min)(n / kNanosPerSecond,
- int64_t{(std::numeric_limits<time_t>::max)()});
- abstime.tv_sec = static_cast<time_t>(seconds);
- abstime.tv_nsec = static_cast<decltype(abstime.tv_nsec)>(n % kNanosPerSecond);
- return abstime;
-}
-
-inline int64_t KernelTimeout::MakeAbsNanos() const {
- if (ns_ == 0) {
- ABSL_RAW_LOG(
- ERROR, "Tried to create a timeout from a non-timeout; never do this.");
- // But we'll try to continue sanely. no-timeout ~= saturated timeout.
- return (std::numeric_limits<int64_t>::max)();
- }
-
- return ns_;
-}
+ private:
+ // Internal representation.
+ // - If the value is kNoTimeout, then the timeout is infinite, and
+ // has_timeout() will return true.
+ // - If the low bit is 0, then the high 63 bits is number of nanoseconds
+ // after the unix epoch.
+ // - If the low bit is 1, then the high 63 bits is a relative duration in
+ // nanoseconds.
+ uint64_t rep_;
+
+ // Returns the number of nanoseconds stored in the internal representation.
+ // Together with is_absolute_timeout() and is_relative_timeout(), the return
+ // value is used to compute when the timeout should occur.
+ int64_t RawNanos() const { return static_cast<int64_t>(rep_ >> 1); }
+
+ // A value that represents no timeout (or an infinite timeout).
+ static constexpr uint64_t kNoTimeout = (std::numeric_limits<uint64_t>::max)();
+
+ // The maximum value that can be stored in the high 63 bits.
+ static constexpr int64_t kMaxNanos = (std::numeric_limits<int64_t>::max)();
+};
} // namespace synchronization_internal
ABSL_NAMESPACE_END
diff --git a/absl/synchronization/internal/kernel_timeout_test.cc b/absl/synchronization/internal/kernel_timeout_test.cc
new file mode 100644
index 00000000..a89ae220
--- /dev/null
+++ b/absl/synchronization/internal/kernel_timeout_test.cc
@@ -0,0 +1,278 @@
+// Copyright 2023 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
+//
+// https://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 "absl/synchronization/internal/kernel_timeout.h"
+
+#include <limits>
+
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+
+namespace {
+
+#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
+ defined(ABSL_HAVE_MEMORY_SANITIZER) || \
+ defined(ABSL_HAVE_THREAD_SANITIZER) || defined(__ANDROID__)
+constexpr absl::Duration kTimingBound = absl::Milliseconds(5);
+#else
+constexpr absl::Duration kTimingBound = absl::Microseconds(250);
+#endif
+
+using absl::synchronization_internal::KernelTimeout;
+
+TEST(KernelTimeout, FiniteTimes) {
+ constexpr absl::Duration kDurationsToTest[] = {
+ absl::ZeroDuration(),
+ absl::Nanoseconds(1),
+ absl::Microseconds(1),
+ absl::Milliseconds(1),
+ absl::Seconds(1),
+ absl::Minutes(1),
+ absl::Hours(1),
+ absl::Hours(1000),
+ -absl::Nanoseconds(1),
+ -absl::Microseconds(1),
+ -absl::Milliseconds(1),
+ -absl::Seconds(1),
+ -absl::Minutes(1),
+ -absl::Hours(1),
+ -absl::Hours(1000),
+ };
+
+ for (auto duration : kDurationsToTest) {
+ const absl::Time now = absl::Now();
+ const absl::Time when = now + duration;
+ SCOPED_TRACE(duration);
+ KernelTimeout t(when);
+ EXPECT_TRUE(t.has_timeout());
+ EXPECT_TRUE(t.is_absolute_timeout());
+ EXPECT_FALSE(t.is_relative_timeout());
+ EXPECT_EQ(absl::TimeFromTimespec(t.MakeAbsTimespec()), when);
+ // MakeRelativeTimespec() doesn't quite round trip when using an absolute
+ // time, but it should get pretty close. Past times are converted to zero
+ // durations.
+ EXPECT_LE(
+ absl::AbsDuration(absl::DurationFromTimespec(t.MakeRelativeTimespec()) -
+ std::max(duration, absl::ZeroDuration())),
+ kTimingBound);
+ EXPECT_EQ(absl::FromUnixNanos(t.MakeAbsNanos()), when);
+ EXPECT_LE(absl::AbsDuration(absl::Milliseconds(t.InMillisecondsFromNow()) -
+ std::max(duration, absl::ZeroDuration())),
+ absl::Milliseconds(5));
+ }
+}
+
+TEST(KernelTimeout, InfiniteFuture) {
+ KernelTimeout t(absl::InfiniteFuture());
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+}
+
+TEST(KernelTimeout, DefaultConstructor) {
+ // The default constructor is equivalent to absl::InfiniteFuture().
+ KernelTimeout t;
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+}
+
+TEST(KernelTimeout, TimeMaxNanos) {
+ // Time >= kMaxNanos should behave as no timeout.
+ KernelTimeout t(absl::FromUnixNanos(std::numeric_limits<int64_t>::max()));
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+}
+
+TEST(KernelTimeout, Never) {
+ // KernelTimeout::Never() is equivalent to absl::InfiniteFuture().
+ KernelTimeout t = KernelTimeout::Never();
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+}
+
+TEST(KernelTimeout, InfinitePast) {
+ KernelTimeout t(absl::InfinitePast());
+ EXPECT_TRUE(t.has_timeout());
+ EXPECT_TRUE(t.is_absolute_timeout());
+ EXPECT_FALSE(t.is_relative_timeout());
+ EXPECT_LE(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::FromUnixNanos(1));
+ EXPECT_EQ(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::ZeroDuration());
+ EXPECT_LE(absl::FromUnixNanos(t.MakeAbsNanos()), absl::FromUnixNanos(1));
+ EXPECT_EQ(t.InMillisecondsFromNow(), KernelTimeout::DWord{0});
+}
+
+TEST(KernelTimeout, FiniteDurations) {
+ constexpr absl::Duration kDurationsToTest[] = {
+ absl::ZeroDuration(),
+ absl::Nanoseconds(1),
+ absl::Microseconds(1),
+ absl::Milliseconds(1),
+ absl::Seconds(1),
+ absl::Minutes(1),
+ absl::Hours(1),
+ absl::Hours(1000),
+ };
+
+ for (auto duration : kDurationsToTest) {
+ SCOPED_TRACE(duration);
+ KernelTimeout t(duration);
+ EXPECT_TRUE(t.has_timeout());
+ EXPECT_FALSE(t.is_absolute_timeout());
+ EXPECT_TRUE(t.is_relative_timeout());
+ EXPECT_LE(absl::AbsDuration(absl::Now() + duration -
+ absl::TimeFromTimespec(t.MakeAbsTimespec())),
+ absl::Milliseconds(5));
+ EXPECT_EQ(absl::DurationFromTimespec(t.MakeRelativeTimespec()), duration);
+ EXPECT_LE(absl::AbsDuration(absl::Now() + duration -
+ absl::FromUnixNanos(t.MakeAbsNanos())),
+ absl::Milliseconds(5));
+ EXPECT_LE(absl::Milliseconds(t.InMillisecondsFromNow()) - duration,
+ absl::Milliseconds(5));
+ }
+}
+
+TEST(KernelTimeout, NegativeDurations) {
+ constexpr absl::Duration kDurationsToTest[] = {
+ -absl::ZeroDuration(),
+ -absl::Nanoseconds(1),
+ -absl::Microseconds(1),
+ -absl::Milliseconds(1),
+ -absl::Seconds(1),
+ -absl::Minutes(1),
+ -absl::Hours(1),
+ -absl::Hours(1000),
+ -absl::InfiniteDuration(),
+ };
+
+ for (auto duration : kDurationsToTest) {
+ // Negative durations should all be converted to zero durations or "now".
+ SCOPED_TRACE(duration);
+ KernelTimeout t(duration);
+ EXPECT_TRUE(t.has_timeout());
+ EXPECT_FALSE(t.is_absolute_timeout());
+ EXPECT_TRUE(t.is_relative_timeout());
+ EXPECT_LE(absl::AbsDuration(absl::Now() -
+ absl::TimeFromTimespec(t.MakeAbsTimespec())),
+ absl::Milliseconds(5));
+ EXPECT_EQ(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::ZeroDuration());
+ EXPECT_LE(
+ absl::AbsDuration(absl::Now() - absl::FromUnixNanos(t.MakeAbsNanos())),
+ absl::Milliseconds(5));
+ EXPECT_EQ(t.InMillisecondsFromNow(), KernelTimeout::DWord{0});
+ }
+}
+
+TEST(KernelTimeout, InfiniteDuration) {
+ KernelTimeout t(absl::InfiniteDuration());
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+}
+
+TEST(KernelTimeout, DurationMaxNanos) {
+ // Duration >= kMaxNanos should behave as no timeout.
+ KernelTimeout t(absl::Nanoseconds(std::numeric_limits<int64_t>::max()));
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+}
+
+TEST(KernelTimeout, OverflowNanos) {
+ // Test what happens when KernelTimeout is constructed with an absl::Duration
+ // that would overflow now_nanos + duration.
+ int64_t now_nanos = absl::ToUnixNanos(absl::Now());
+ int64_t limit = std::numeric_limits<int64_t>::max() - now_nanos;
+ absl::Duration duration = absl::Nanoseconds(limit) + absl::Seconds(1);
+ KernelTimeout t(duration);
+ EXPECT_TRUE(t.has_timeout());
+ // Timeouts should still be far in the future.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_LE(absl::Milliseconds(t.InMillisecondsFromNow()) - duration,
+ absl::Milliseconds(5));
+}
+
+} // namespace