Metadata+GRPC.swift 3.0 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. extension Metadata {
  17. @inlinable
  18. var previousRPCAttempts: Int? {
  19. get {
  20. self.firstString(forKey: .previousRPCAttempts).flatMap { Int($0) }
  21. }
  22. set {
  23. if let newValue = newValue {
  24. self.replaceOrAddString(String(describing: newValue), forKey: .previousRPCAttempts)
  25. } else {
  26. self.removeAllValues(forKey: .previousRPCAttempts)
  27. }
  28. }
  29. }
  30. @inlinable
  31. var retryPushback: RetryPushback? {
  32. return self.firstString(forKey: .retryPushbackMs).map {
  33. RetryPushback(milliseconds: $0)
  34. }
  35. }
  36. @inlinable
  37. var timeout: Duration? {
  38. get {
  39. self.firstString(forKey: .timeout).flatMap { Timeout(decoding: $0)?.duration }
  40. }
  41. set {
  42. if let newValue {
  43. self.replaceOrAddString(String(describing: Timeout(duration: newValue)), forKey: .timeout)
  44. } else {
  45. self.removeAllValues(forKey: .timeout)
  46. }
  47. }
  48. }
  49. }
  50. extension Metadata {
  51. @usableFromInline
  52. enum GRPCKey: String, Sendable, Hashable {
  53. case timeout = "grpc-timeout"
  54. case retryPushbackMs = "grpc-retry-pushback-ms"
  55. case previousRPCAttempts = "grpc-previous-rpc-attempts"
  56. }
  57. @inlinable
  58. func firstString(forKey key: GRPCKey) -> String? {
  59. self[stringValues: key.rawValue].first(where: { _ in true })
  60. }
  61. @inlinable
  62. mutating func replaceOrAddString(_ value: String, forKey key: GRPCKey) {
  63. self.replaceOrAddString(value, forKey: key.rawValue)
  64. }
  65. @inlinable
  66. mutating func removeAllValues(forKey key: GRPCKey) {
  67. self.removeAllValues(forKey: key.rawValue)
  68. }
  69. }
  70. extension Metadata {
  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/0e1807a6e30a1a915c0dcadc873bca92b9fa9720/A6-client-retries.md
  86. self = .stopRetrying
  87. }
  88. }
  89. }
  90. }