Commit fa485540 by Derek Mauro Committed by Copybara-Service

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
parent 823b8378
......@@ -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"
......
......@@ -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 = [
......
......@@ -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
......
// 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
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment