about summary refs log tree commit diff
path: root/absl/synchronization/internal/kernel_timeout.h
blob: 0d132d98f886eec33428ee199982b07c40fbcbf3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright 2017 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
//
//      http://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.
//

// 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 that are friends of this class. 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_

#ifdef _WIN32
#include <intsafe.h>
#endif
#include <time.h>
#include <algorithm>
#include <limits>

#include "absl/base/internal/raw_logging.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"

namespace absl {
namespace synchronization_internal {

class Futex;
class Waiter;

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) {}

  // A more explicit factory for those who prefer it.  Equivalent to {}.
  static KernelTimeout Never() { return {}; }

  // We explicitly do not support other custom formats: timespec, int64_t nanos.
  // Unify on this and absl::Time, please.
  bool has_timeout() const { return ns_ != 0; }

 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;
  }

  // Convert to parameter for sem_timedwait/futex/similar.  Only for approved
  // users.  Do not call if !has_timeout.
  struct timespec MakeAbsTimespec() {
    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;
  }

#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
  // relative duration (it should be recomputed by calling this method
  // in the case of a spurious wakeup).
  DWORD InMillisecondsFromNow() const {
    if (!has_timeout()) {
      return INFINITE;
    }
    // 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<uint64_t>(max_nanos, ns_ - now) + 999999u) / 1000000u;
      if (ms_from_now > std::numeric_limits<DWORD>::max()) {
        return INFINITE;
      }
      return static_cast<DWORD>(ms_from_now);
    }
    return 0;
  }
#endif

  friend class Futex;
  friend class Waiter;
};

}  // namespace synchronization_internal
}  // namespace absl
#endif  // ABSL_SYNCHRONIZATION_INTERNAL_KERNEL_TIMEOUT_H_