GRPCTimeout.swift 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  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 NIO
  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 = 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. let timeAmountUntilDeadline = deadline - (testingOnlyNow ?? .now())
  41. self.init(rounding: timeAmountUntilDeadline.nanoseconds, unit: .nanoseconds)
  42. }
  43. private init(nanoseconds: Int64, wireEncoding: String) {
  44. self.nanoseconds = nanoseconds
  45. self.wireEncoding = wireEncoding
  46. }
  47. /// Creates a `GRPCTimeout`.
  48. ///
  49. /// - Precondition: The amount should be greater than or equal to zero and less than or equal
  50. /// to `GRPCTimeout.maxAmount`.
  51. internal init(amount: Int64, unit: GRPCTimeoutUnit) {
  52. precondition(amount >= 0 && amount <= GRPCTimeout.maxAmount)
  53. // See "Timeout" in https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
  54. // If we overflow at this point, which is certainly possible if `amount` is sufficiently large
  55. // and `unit` is `.hours`, clamp the nanosecond timeout to `Int64.max`. It's about 292 years so
  56. // it should be long enough for the user not to notice the difference should the rpc time out.
  57. let (partial, overflow) = amount.multipliedReportingOverflow(by: unit.asNanoseconds)
  58. self.init(
  59. nanoseconds: overflow ? Int64.max : partial,
  60. wireEncoding: "\(amount)\(unit.rawValue)"
  61. )
  62. }
  63. /// Create a timeout by rounding up the timeout so that it may be represented in the gRPC
  64. /// wire format.
  65. internal init(rounding amount: Int64, unit: GRPCTimeoutUnit) {
  66. var roundedAmount = amount
  67. var roundedUnit = unit
  68. if roundedAmount <= 0 {
  69. roundedAmount = 0
  70. } else {
  71. while roundedAmount > GRPCTimeout.maxAmount {
  72. switch roundedUnit {
  73. case .nanoseconds:
  74. roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000)
  75. roundedUnit = .microseconds
  76. case .microseconds:
  77. roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000)
  78. roundedUnit = .milliseconds
  79. case .milliseconds:
  80. roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000)
  81. roundedUnit = .seconds
  82. case .seconds:
  83. roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 60)
  84. roundedUnit = .minutes
  85. case .minutes:
  86. roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 60)
  87. roundedUnit = .hours
  88. case .hours:
  89. roundedAmount = GRPCTimeout.maxAmount
  90. roundedUnit = .hours
  91. }
  92. }
  93. }
  94. self.init(amount: roundedAmount, unit: roundedUnit)
  95. }
  96. }
  97. private extension Int64 {
  98. /// Returns the quotient of this value when divided by `divisor` rounded up to the nearest
  99. /// multiple of `divisor` if the remainder is non-zero.
  100. ///
  101. /// - Parameter divisor: The value to divide this value by.
  102. func quotientRoundedUp(dividingBy divisor: Int64) -> Int64 {
  103. let (quotient, remainder) = self.quotientAndRemainder(dividingBy: divisor)
  104. return quotient + (remainder != 0 ? 1 : 0)
  105. }
  106. }
  107. internal enum GRPCTimeoutUnit: String {
  108. case hours = "H"
  109. case minutes = "M"
  110. case seconds = "S"
  111. case milliseconds = "m"
  112. case microseconds = "u"
  113. case nanoseconds = "n"
  114. internal var asNanoseconds: Int64 {
  115. switch self {
  116. case .hours:
  117. return 60 * 60 * 1000 * 1000 * 1000
  118. case .minutes:
  119. return 60 * 1000 * 1000 * 1000
  120. case .seconds:
  121. return 1000 * 1000 * 1000
  122. case .milliseconds:
  123. return 1000 * 1000
  124. case .microseconds:
  125. return 1000
  126. case .nanoseconds:
  127. return 1
  128. }
  129. }
  130. }