/src/grpc-swift/Sources/GRPC/GRPCTimeout.swift
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2019, gRPC Authors All rights reserved. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | import Dispatch |
17 | | import NIOCore |
18 | | |
19 | | /// A timeout for a gRPC call. |
20 | | /// |
21 | | /// Timeouts must be positive and at most 8-digits long. |
22 | | public struct GRPCTimeout: CustomStringConvertible, Equatable { |
23 | | /// Creates an infinite timeout. This is a sentinel value which must __not__ be sent to a gRPC service. |
24 | | public static let infinite = GRPCTimeout( |
25 | | nanoseconds: Int64.max, |
26 | | wireEncoding: "infinite" |
27 | | ) |
28 | | |
29 | | /// The largest amount of any unit of time which may be represented by a gRPC timeout. |
30 | | internal static let maxAmount: Int64 = 99_999_999 |
31 | | |
32 | | /// The wire encoding of this timeout as described in the gRPC protocol. |
33 | | /// See: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md. |
34 | | public let wireEncoding: String |
35 | | public let nanoseconds: Int64 |
36 | | |
37 | 0 | public var description: String { |
38 | 0 | return self.wireEncoding |
39 | 0 | } |
40 | | |
41 | | /// Creates a timeout from the given deadline. |
42 | | /// |
43 | | /// - Parameter deadline: The deadline to create a timeout from. |
44 | 0 | internal init(deadline: NIODeadline, testingOnlyNow: NIODeadline? = nil) { |
45 | 0 | switch deadline { |
46 | 0 | case .distantFuture: |
47 | 0 | self = .infinite |
48 | 0 | default: |
49 | 0 | let timeAmountUntilDeadline = deadline - (testingOnlyNow ?? .now()) |
50 | 0 | self.init(rounding: timeAmountUntilDeadline.nanoseconds, unit: .nanoseconds) |
51 | 0 | } |
52 | 0 | } |
53 | | |
54 | 0 | private init(nanoseconds: Int64, wireEncoding: String) { |
55 | 0 | self.nanoseconds = nanoseconds |
56 | 0 | self.wireEncoding = wireEncoding |
57 | 0 | } |
58 | | |
59 | | /// Creates a `GRPCTimeout`. |
60 | | /// |
61 | | /// - Precondition: The amount should be greater than or equal to zero and less than or equal |
62 | | /// to `GRPCTimeout.maxAmount`. |
63 | 0 | internal init(amount: Int64, unit: GRPCTimeoutUnit) { |
64 | 0 | precondition(amount >= 0 && amount <= GRPCTimeout.maxAmount) |
65 | 0 | // See "Timeout" in https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests |
66 | 0 |
|
67 | 0 | // If we overflow at this point, which is certainly possible if `amount` is sufficiently large |
68 | 0 | // and `unit` is `.hours`, clamp the nanosecond timeout to `Int64.max`. It's about 292 years so |
69 | 0 | // it should be long enough for the user not to notice the difference should the rpc time out. |
70 | 0 | let (partial, overflow) = amount.multipliedReportingOverflow(by: unit.asNanoseconds) |
71 | 0 |
|
72 | 0 | self.init( |
73 | 0 | nanoseconds: overflow ? Int64.max : partial, |
74 | 0 | wireEncoding: "\(amount)\(unit.rawValue)" |
75 | 0 | ) |
76 | 0 | } |
77 | | |
78 | | /// Create a timeout by rounding up the timeout so that it may be represented in the gRPC |
79 | | /// wire format. |
80 | 0 | internal init(rounding amount: Int64, unit: GRPCTimeoutUnit) { |
81 | 0 | var roundedAmount = amount |
82 | 0 | var roundedUnit = unit |
83 | 0 |
|
84 | 0 | if roundedAmount <= 0 { |
85 | 0 | roundedAmount = 0 |
86 | 0 | } else { |
87 | 0 | while roundedAmount > GRPCTimeout.maxAmount { |
88 | 0 | switch roundedUnit { |
89 | 0 | case .nanoseconds: |
90 | 0 | roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000) |
91 | 0 | roundedUnit = .microseconds |
92 | 0 | case .microseconds: |
93 | 0 | roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000) |
94 | 0 | roundedUnit = .milliseconds |
95 | 0 | case .milliseconds: |
96 | 0 | roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000) |
97 | 0 | roundedUnit = .seconds |
98 | 0 | case .seconds: |
99 | 0 | roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 60) |
100 | 0 | roundedUnit = .minutes |
101 | 0 | case .minutes: |
102 | 0 | roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 60) |
103 | 0 | roundedUnit = .hours |
104 | 0 | case .hours: |
105 | 0 | roundedAmount = GRPCTimeout.maxAmount |
106 | 0 | roundedUnit = .hours |
107 | 0 | } |
108 | 0 | } |
109 | 0 | } |
110 | 0 |
|
111 | 0 | self.init(amount: roundedAmount, unit: roundedUnit) |
112 | 0 | } |
113 | | } |
114 | | |
115 | | extension Int64 { |
116 | | /// Returns the quotient of this value when divided by `divisor` rounded up to the nearest |
117 | | /// multiple of `divisor` if the remainder is non-zero. |
118 | | /// |
119 | | /// - Parameter divisor: The value to divide this value by. |
120 | 0 | fileprivate func quotientRoundedUp(dividingBy divisor: Int64) -> Int64 { |
121 | 0 | let (quotient, remainder) = self.quotientAndRemainder(dividingBy: divisor) |
122 | 0 | return quotient + (remainder != 0 ? 1 : 0) |
123 | 0 | } |
124 | | } |
125 | | |
126 | | internal enum GRPCTimeoutUnit: String { |
127 | | case hours = "H" |
128 | | case minutes = "M" |
129 | | case seconds = "S" |
130 | | case milliseconds = "m" |
131 | | case microseconds = "u" |
132 | | case nanoseconds = "n" |
133 | | |
134 | 0 | internal var asNanoseconds: Int64 { |
135 | 0 | switch self { |
136 | 0 | case .hours: |
137 | 0 | return 60 * 60 * 1000 * 1000 * 1000 |
138 | 0 |
|
139 | 0 | case .minutes: |
140 | 0 | return 60 * 1000 * 1000 * 1000 |
141 | 0 |
|
142 | 0 | case .seconds: |
143 | 0 | return 1000 * 1000 * 1000 |
144 | 0 |
|
145 | 0 | case .milliseconds: |
146 | 0 | return 1000 * 1000 |
147 | 0 |
|
148 | 0 | case .microseconds: |
149 | 0 | return 1000 |
150 | 0 |
|
151 | 0 | case .nanoseconds: |
152 | 0 | return 1 |
153 | 0 | } |
154 | 0 | } |
155 | | } |