| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- /*
- * Copyright 2019, gRPC Authors All rights reserved.
- *
- * 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.
- */
- import Dispatch
- import NIOCore
- /// A timeout for a gRPC call.
- ///
- /// Timeouts must be positive and at most 8-digits long.
- public struct GRPCTimeout: CustomStringConvertible, Equatable {
- /// Creates an infinite timeout. This is a sentinel value which must __not__ be sent to a gRPC service.
- public static let infinite = GRPCTimeout(
- nanoseconds: Int64.max,
- wireEncoding: "infinite"
- )
- /// The largest amount of any unit of time which may be represented by a gRPC timeout.
- internal static let maxAmount: Int64 = 99_999_999
- /// The wire encoding of this timeout as described in the gRPC protocol.
- /// See: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md.
- public let wireEncoding: String
- public let nanoseconds: Int64
- public var description: String {
- return self.wireEncoding
- }
- /// Creates a timeout from the given deadline.
- ///
- /// - Parameter deadline: The deadline to create a timeout from.
- internal init(deadline: NIODeadline, testingOnlyNow: NIODeadline? = nil) {
- switch deadline {
- case .distantFuture:
- self = .infinite
- default:
- let timeAmountUntilDeadline = deadline - (testingOnlyNow ?? .now())
- self.init(rounding: timeAmountUntilDeadline.nanoseconds, unit: .nanoseconds)
- }
- }
- private init(nanoseconds: Int64, wireEncoding: String) {
- self.nanoseconds = nanoseconds
- self.wireEncoding = wireEncoding
- }
- /// Creates a `GRPCTimeout`.
- ///
- /// - Precondition: The amount should be greater than or equal to zero and less than or equal
- /// to `GRPCTimeout.maxAmount`.
- internal init(amount: Int64, unit: GRPCTimeoutUnit) {
- precondition(amount >= 0 && amount <= GRPCTimeout.maxAmount)
- // See "Timeout" in https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
- // If we overflow at this point, which is certainly possible if `amount` is sufficiently large
- // and `unit` is `.hours`, clamp the nanosecond timeout to `Int64.max`. It's about 292 years so
- // it should be long enough for the user not to notice the difference should the rpc time out.
- let (partial, overflow) = amount.multipliedReportingOverflow(by: unit.asNanoseconds)
- self.init(
- nanoseconds: overflow ? Int64.max : partial,
- wireEncoding: "\(amount)\(unit.rawValue)"
- )
- }
- /// Create a timeout by rounding up the timeout so that it may be represented in the gRPC
- /// wire format.
- internal init(rounding amount: Int64, unit: GRPCTimeoutUnit) {
- var roundedAmount = amount
- var roundedUnit = unit
- if roundedAmount <= 0 {
- roundedAmount = 0
- } else {
- while roundedAmount > GRPCTimeout.maxAmount {
- switch roundedUnit {
- case .nanoseconds:
- roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000)
- roundedUnit = .microseconds
- case .microseconds:
- roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000)
- roundedUnit = .milliseconds
- case .milliseconds:
- roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000)
- roundedUnit = .seconds
- case .seconds:
- roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 60)
- roundedUnit = .minutes
- case .minutes:
- roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 60)
- roundedUnit = .hours
- case .hours:
- roundedAmount = GRPCTimeout.maxAmount
- roundedUnit = .hours
- }
- }
- }
- self.init(amount: roundedAmount, unit: roundedUnit)
- }
- }
- extension Int64 {
- /// Returns the quotient of this value when divided by `divisor` rounded up to the nearest
- /// multiple of `divisor` if the remainder is non-zero.
- ///
- /// - Parameter divisor: The value to divide this value by.
- fileprivate func quotientRoundedUp(dividingBy divisor: Int64) -> Int64 {
- let (quotient, remainder) = self.quotientAndRemainder(dividingBy: divisor)
- return quotient + (remainder != 0 ? 1 : 0)
- }
- }
- internal enum GRPCTimeoutUnit: String {
- case hours = "H"
- case minutes = "M"
- case seconds = "S"
- case milliseconds = "m"
- case microseconds = "u"
- case nanoseconds = "n"
- internal var asNanoseconds: Int64 {
- switch self {
- case .hours:
- return 60 * 60 * 1000 * 1000 * 1000
- case .minutes:
- return 60 * 1000 * 1000 * 1000
- case .seconds:
- return 1000 * 1000 * 1000
- case .milliseconds:
- return 1000 * 1000
- case .microseconds:
- return 1000
- case .nanoseconds:
- return 1
- }
- }
- }
|