summaryrefslogtreecommitdiff
path: root/absl/synchronization/internal/kernel_timeout.cc
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 /absl/synchronization/internal/kernel_timeout.cc
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
Diffstat (limited to 'absl/synchronization/internal/kernel_timeout.cc')
-rw-r--r--absl/synchronization/internal/kernel_timeout.cc168
1 files changed, 168 insertions, 0 deletions
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