Metadata+GRPC.swift 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /*
  2. * Copyright 2023, 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. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  17. extension Metadata {
  18. @inlinable
  19. var previousRPCAttempts: Int? {
  20. get {
  21. self.firstString(forKey: .previousRPCAttempts).flatMap { Int($0) }
  22. }
  23. set {
  24. if let newValue = newValue {
  25. self.replaceOrAddString(String(describing: newValue), forKey: .previousRPCAttempts)
  26. } else {
  27. self.removeAllValues(forKey: .previousRPCAttempts)
  28. }
  29. }
  30. }
  31. @inlinable
  32. var retryPushback: RetryPushback? {
  33. return self.firstString(forKey: .retryPushbackMs).map {
  34. RetryPushback(milliseconds: $0)
  35. }
  36. }
  37. @inlinable
  38. var timeout: Duration? {
  39. // Temporary hack to support tests; only supports nanoseconds.
  40. guard let value = self.firstString(forKey: .timeout) else { return nil }
  41. guard value.utf8.last == UTF8.CodeUnit(ascii: "n") else { return nil }
  42. var index = value.utf8.endIndex
  43. value.utf8.formIndex(before: &index)
  44. guard let digits = String(value.utf8[..<index]) else { return nil }
  45. guard let nanoseconds = Int64(digits) else { return nil }
  46. return .nanoseconds(nanoseconds)
  47. }
  48. }
  49. extension Metadata {
  50. @usableFromInline
  51. enum GRPCKey: String, Sendable, Hashable {
  52. case timeout = "grpc-timeout"
  53. case retryPushbackMs = "grpc-retry-pushback-ms"
  54. case previousRPCAttempts = "grpc-previous-rpc-attempts"
  55. }
  56. @inlinable
  57. func firstString(forKey key: GRPCKey) -> String? {
  58. self[stringValues: key.rawValue].first(where: { _ in true })
  59. }
  60. @inlinable
  61. mutating func replaceOrAddString(_ value: String, forKey key: GRPCKey) {
  62. self.replaceOrAddString(value, forKey: key.rawValue)
  63. }
  64. @inlinable
  65. mutating func removeAllValues(forKey key: GRPCKey) {
  66. self.removeAllValues(forKey: key.rawValue)
  67. }
  68. }
  69. extension Metadata {
  70. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  71. @usableFromInline
  72. enum RetryPushback: Hashable, Sendable {
  73. case retryAfter(Duration)
  74. case stopRetrying
  75. @inlinable
  76. init(milliseconds value: String) {
  77. if let milliseconds = Int64(value), milliseconds >= 0 {
  78. let (seconds, remainingMilliseconds) = milliseconds.quotientAndRemainder(dividingBy: 1000)
  79. // 1e18 attoseconds per second
  80. // 1e15 attoseconds per millisecond.
  81. let attoseconds = Int64(remainingMilliseconds) * 1_000_000_000_000_000
  82. self = .retryAfter(Duration(secondsComponent: seconds, attosecondsComponent: attoseconds))
  83. } else {
  84. // Negative or not parseable means stop trying.
  85. // Source: https://github.com/grpc/proposal/blob/master/A6-client-retries.md
  86. self = .stopRetrying
  87. }
  88. }
  89. }
  90. }