GRPCTimeout.swift 5.0 KB

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