kernel_timeout.cc (6434B)
1 // Copyright 2023 The Abseil Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "absl/synchronization/internal/kernel_timeout.h" 16 17 #ifndef _WIN32 18 #include <sys/types.h> 19 #endif 20 21 #include <algorithm> 22 #include <chrono> // NOLINT(build/c++11) 23 #include <cstdint> 24 #include <cstdlib> 25 #include <cstring> 26 #include <ctime> 27 #include <limits> 28 29 #include "absl/base/attributes.h" 30 #include "absl/base/call_once.h" 31 #include "absl/base/config.h" 32 #include "absl/time/time.h" 33 34 namespace absl { 35 ABSL_NAMESPACE_BEGIN 36 namespace synchronization_internal { 37 38 int64_t KernelTimeout::SteadyClockNow() { 39 if (!SupportsSteadyClock()) { 40 return absl::GetCurrentTimeNanos(); 41 } 42 return std::chrono::duration_cast<std::chrono::nanoseconds>( 43 std::chrono::steady_clock::now().time_since_epoch()) 44 .count(); 45 } 46 47 KernelTimeout::KernelTimeout(absl::Time t) { 48 // `absl::InfiniteFuture()` is a common "no timeout" value and cheaper to 49 // compare than convert. 50 if (t == absl::InfiniteFuture()) { 51 rep_ = kNoTimeout; 52 return; 53 } 54 55 int64_t unix_nanos = absl::ToUnixNanos(t); 56 57 // A timeout that lands before the unix epoch is converted to 0. 58 // In theory implementations should expire these timeouts immediately. 59 if (unix_nanos < 0) { 60 unix_nanos = 0; 61 } 62 63 // Values greater than or equal to kMaxNanos are converted to infinite. 64 if (unix_nanos >= kMaxNanos) { 65 rep_ = kNoTimeout; 66 return; 67 } 68 69 rep_ = static_cast<uint64_t>(unix_nanos) << 1; 70 } 71 72 KernelTimeout::KernelTimeout(absl::Duration d) { 73 // `absl::InfiniteDuration()` is a common "no timeout" value and cheaper to 74 // compare than convert. 75 if (d == absl::InfiniteDuration()) { 76 rep_ = kNoTimeout; 77 return; 78 } 79 80 int64_t nanos = absl::ToInt64Nanoseconds(d); 81 82 // Negative durations are normalized to 0. 83 // In theory implementations should expire these timeouts immediately. 84 if (nanos < 0) { 85 nanos = 0; 86 } 87 88 int64_t now = SteadyClockNow(); 89 if (nanos > kMaxNanos - now) { 90 // Durations that would be greater than kMaxNanos are converted to infinite. 91 rep_ = kNoTimeout; 92 return; 93 } 94 95 nanos += now; 96 rep_ = (static_cast<uint64_t>(nanos) << 1) | uint64_t{1}; 97 } 98 99 int64_t KernelTimeout::MakeAbsNanos() const { 100 if (!has_timeout()) { 101 return kMaxNanos; 102 } 103 104 int64_t nanos = RawAbsNanos(); 105 106 if (is_relative_timeout()) { 107 // We need to change epochs, because the relative timeout might be 108 // represented by an absolute timestamp from another clock. 109 nanos = std::max<int64_t>(nanos - SteadyClockNow(), 0); 110 int64_t now = absl::GetCurrentTimeNanos(); 111 if (nanos > kMaxNanos - now) { 112 // Overflow. 113 nanos = kMaxNanos; 114 } else { 115 nanos += now; 116 } 117 } else if (nanos == 0) { 118 // Some callers have assumed that 0 means no timeout, so instead we return a 119 // time of 1 nanosecond after the epoch. 120 nanos = 1; 121 } 122 123 return nanos; 124 } 125 126 int64_t KernelTimeout::InNanosecondsFromNow() const { 127 if (!has_timeout()) { 128 return kMaxNanos; 129 } 130 131 int64_t nanos = RawAbsNanos(); 132 if (is_absolute_timeout()) { 133 return std::max<int64_t>(nanos - absl::GetCurrentTimeNanos(), 0); 134 } 135 return std::max<int64_t>(nanos - SteadyClockNow(), 0); 136 } 137 138 struct timespec KernelTimeout::MakeAbsTimespec() const { 139 return absl::ToTimespec(absl::Nanoseconds(MakeAbsNanos())); 140 } 141 142 struct timespec KernelTimeout::MakeRelativeTimespec() const { 143 return absl::ToTimespec(absl::Nanoseconds(InNanosecondsFromNow())); 144 } 145 146 #ifndef _WIN32 147 struct timespec KernelTimeout::MakeClockAbsoluteTimespec(clockid_t c) const { 148 if (!has_timeout()) { 149 return absl::ToTimespec(absl::Nanoseconds(kMaxNanos)); 150 } 151 152 int64_t nanos = RawAbsNanos(); 153 if (is_absolute_timeout()) { 154 nanos -= absl::GetCurrentTimeNanos(); 155 } else { 156 nanos -= SteadyClockNow(); 157 } 158 159 struct timespec now; 160 ABSL_RAW_CHECK(clock_gettime(c, &now) == 0, "clock_gettime() failed"); 161 absl::Duration from_clock_epoch = 162 absl::DurationFromTimespec(now) + absl::Nanoseconds(nanos); 163 if (from_clock_epoch <= absl::ZeroDuration()) { 164 // Some callers have assumed that 0 means no timeout, so instead we return a 165 // time of 1 nanosecond after the epoch. For safety we also do not return 166 // negative values. 167 return absl::ToTimespec(absl::Nanoseconds(1)); 168 } 169 return absl::ToTimespec(from_clock_epoch); 170 } 171 #endif 172 173 KernelTimeout::DWord KernelTimeout::InMillisecondsFromNow() const { 174 constexpr DWord kInfinite = std::numeric_limits<DWord>::max(); 175 176 if (!has_timeout()) { 177 return kInfinite; 178 } 179 180 constexpr uint64_t kNanosInMillis = uint64_t{1'000'000}; 181 constexpr uint64_t kMaxValueNanos = 182 std::numeric_limits<int64_t>::max() - kNanosInMillis + 1; 183 184 uint64_t ns_from_now = static_cast<uint64_t>(InNanosecondsFromNow()); 185 if (ns_from_now >= kMaxValueNanos) { 186 // Rounding up would overflow. 187 return kInfinite; 188 } 189 // Convert to milliseconds, always rounding up. 190 uint64_t ms_from_now = (ns_from_now + kNanosInMillis - 1) / kNanosInMillis; 191 if (ms_from_now > kInfinite) { 192 return kInfinite; 193 } 194 return static_cast<DWord>(ms_from_now); 195 } 196 197 std::chrono::time_point<std::chrono::system_clock> 198 KernelTimeout::ToChronoTimePoint() const { 199 if (!has_timeout()) { 200 return std::chrono::time_point<std::chrono::system_clock>::max(); 201 } 202 203 // The cast to std::microseconds is because (on some platforms) the 204 // std::ratio used by std::chrono::steady_clock doesn't convert to 205 // std::nanoseconds, so it doesn't compile. 206 auto micros = std::chrono::duration_cast<std::chrono::microseconds>( 207 std::chrono::nanoseconds(MakeAbsNanos())); 208 return std::chrono::system_clock::from_time_t(0) + micros; 209 } 210 211 std::chrono::nanoseconds KernelTimeout::ToChronoDuration() const { 212 if (!has_timeout()) { 213 return std::chrono::nanoseconds::max(); 214 } 215 return std::chrono::nanoseconds(InNanosecondsFromNow()); 216 } 217 218 } // namespace synchronization_internal 219 ABSL_NAMESPACE_END 220 } // namespace absl