GRPCTimeout.swift 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. /// A timeout for a gRPC call.
  19. ///
  20. /// Timeouts must be positive and at most 8-digits long.
  21. public struct GRPCTimeout: CustomStringConvertible, Equatable {
  22. /// Creates an infinite timeout. This is a sentinel value which must __not__ be sent to a gRPC service.
  23. public static let infinite = GRPCTimeout(
  24. nanoseconds: Int64.max,
  25. wireEncoding: "infinite"
  26. )
  27. /// The largest amount of any unit of time which may be represented by a gRPC timeout.
  28. internal static let maxAmount: Int64 = 99_999_999
  29. /// The wire encoding of this timeout as described in the gRPC protocol.
  30. /// See: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md.
  31. public let wireEncoding: String
  32. public let nanoseconds: Int64
  33. public var description: String {
  34. return self.wireEncoding
  35. }
  36. /// Creates a timeout from the given deadline.
  37. ///
  38. /// - Parameter deadline: The deadline to create a timeout from.
  39. internal init(deadline: NIODeadline, testingOnlyNow: NIODeadline? = nil) {
  40. switch deadline {
  41. case .distantFuture:
  42. self = .infinite
  43. default:
  44. let timeAmountUntilDeadline = deadline - (testingOnlyNow ?? .now())
  45. self.init(rounding: timeAmountUntilDeadline.nanoseconds, unit: .nanoseconds)
  46. }
  47. }
  48. private init(nanoseconds: Int64, wireEncoding: String) {
  49. self.nanoseconds = nanoseconds
  50. self.wireEncoding = wireEncoding
  51. }
  52. /// Creates a `GRPCTimeout`.
  53. ///
  54. /// - Precondition: The amount should be greater than or equal to zero and less than or equal
  55. /// to `GRPCTimeout.maxAmount`.
  56. internal init(amount: Int64, unit: GRPCTimeoutUnit) {
  57. precondition(amount >= 0 && amount <= GRPCTimeout.maxAmount)
  58. // See "Timeout" in https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
  59. // If we overflow at this point, which is certainly possible if `amount` is sufficiently large
  60. // and `unit` is `.hours`, clamp the nanosecond timeout to `Int64.max`. It's about 292 years so
  61. // it should be long enough for the user not to notice the difference should the rpc time out.
  62. let (partial, overflow) = amount.multipliedReportingOverflow(by: unit.asNanoseconds)
  63. self.init(
  64. nanoseconds: overflow ? Int64.max : partial,
  65. wireEncoding: "\(amount)\(unit.rawValue)"
  66. )
  67. }
  68. /// Create a timeout by rounding up the timeout so that it may be represented in the gRPC
  69. /// wire format.
  70. internal init(rounding amount: Int64, unit: GRPCTimeoutUnit) {
  71. var roundedAmount = amount
  72. var roundedUnit = unit
  73. if roundedAmount <= 0 {
  74. roundedAmount = 0
  75. } else {
  76. while roundedAmount > GRPCTimeout.maxAmount {
  77. switch roundedUnit {
  78. case .nanoseconds:
  79. roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000)
  80. roundedUnit = .microseconds
  81. case .microseconds:
  82. roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000)
  83. roundedUnit = .milliseconds
  84. case .milliseconds:
  85. roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000)
  86. roundedUnit = .seconds
  87. case .seconds:
  88. roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 60)
  89. roundedUnit = .minutes
  90. case .minutes:
  91. roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 60)
  92. roundedUnit = .hours
  93. case .hours:
  94. roundedAmount = GRPCTimeout.maxAmount
  95. roundedUnit = .hours
  96. }
  97. }
  98. }
  99. self.init(amount: roundedAmount, unit: roundedUnit)
  100. }
  101. }
  102. extension Int64 {
  103. /// Returns the quotient of this value when divided by `divisor` rounded up to the nearest
  104. /// multiple of `divisor` if the remainder is non-zero.
  105. ///
  106. /// - Parameter divisor: The value to divide this value by.
  107. fileprivate func quotientRoundedUp(dividingBy divisor: Int64) -> Int64 {
  108. let (quotient, remainder) = self.quotientAndRemainder(dividingBy: divisor)
  109. return quotient + (remainder != 0 ? 1 : 0)
  110. }
  111. }
  112. internal enum GRPCTimeoutUnit: String {
  113. case hours = "H"
  114. case minutes = "M"
  115. case seconds = "S"
  116. case milliseconds = "m"
  117. case microseconds = "u"
  118. case nanoseconds = "n"
  119. internal var asNanoseconds: Int64 {
  120. switch self {
  121. case .hours:
  122. return 60 * 60 * 1000 * 1000 * 1000
  123. case .minutes:
  124. return 60 * 1000 * 1000 * 1000
  125. case .seconds:
  126. return 1000 * 1000 * 1000
  127. case .milliseconds:
  128. return 1000 * 1000
  129. case .microseconds:
  130. return 1000
  131. case .nanoseconds:
  132. return 1
  133. }
  134. }
  135. }