@@ -20,13 +20,21 @@ import NIOCore
2020/// The defaults are determined by the gRPC keepalive
2121/// [documentation] (https://github.com/grpc/grpc/blob/master/doc/keepalive.md).
2222public struct ClientConnectionKeepalive : Hashable , Sendable {
23+ private func checkInvariants( line: UInt = #line) {
24+ precondition ( self . timeout < self . interval, " 'timeout' must be less than 'interval' " , line: line)
25+ }
26+
2327 /// The amount of time to wait before sending a keepalive ping.
24- public var interval : TimeAmount
28+ public var interval : TimeAmount {
29+ didSet { self . checkInvariants ( ) }
30+ }
2531
2632 /// The amount of time to wait for an acknowledgment.
2733 /// If it does not receive an acknowledgment within this time, it will close the connection
2834 /// This value must be less than ``interval``.
29- public var timeout : TimeAmount
35+ public var timeout : TimeAmount {
36+ didSet { self . checkInvariants ( ) }
37+ }
3038
3139 /// Send keepalive pings even if there are no calls in flight.
3240 public var permitWithoutCalls : Bool
@@ -45,23 +53,63 @@ public struct ClientConnectionKeepalive: Hashable, Sendable {
4553 maximumPingsWithoutData: UInt = 2 ,
4654 minimumSentPingIntervalWithoutData: TimeAmount = . minutes( 5 )
4755 ) {
48- precondition ( timeout < interval, " `timeout` must be less than `interval` " )
4956 self . interval = interval
5057 self . timeout = timeout
5158 self . permitWithoutCalls = permitWithoutCalls
5259 self . maximumPingsWithoutData = maximumPingsWithoutData
5360 self . minimumSentPingIntervalWithoutData = minimumSentPingIntervalWithoutData
61+ self . checkInvariants ( )
62+ }
63+ }
64+
65+ extension ClientConnectionKeepalive {
66+ /// Applies jitter to the ``interval``.
67+ ///
68+ /// The current ``interval`` will be adjusted by no more than `maxJitter` in either direction,
69+ /// that is the ``interval`` may increase or decrease by no more than `maxJitter`. As
70+ /// the ``timeout`` must be strictly less than the ``interval``, the lower range of the jittered
71+ /// interval is clamped to `max(interval - maxJitter, timeout + .nanoseconds(1)))`.
72+ ///
73+ /// - Parameter maxJitter: The maximum amount of jitter to apply to the ``interval``, which may
74+ /// be applied in either direction.
75+ public mutating func jitterInterval( byAtMost maxJitter: TimeAmount ) {
76+ // The interval must be larger than the timeout so clamp the lower bound to be greater than
77+ // the timeout.
78+ let lowerBound = max ( self . interval - maxJitter, self . timeout + . nanoseconds( 1 ) )
79+ let upperBound = self . interval + maxJitter
80+ self . interval = . nanoseconds( . random( in: lowerBound. nanoseconds ... upperBound. nanoseconds) )
81+ }
82+
83+ /// Returns a new ``ClientConnectionKeepalive`` with a jittered ``interval``.
84+ ///
85+ /// See also ``jitterInterval(byAtMost:)``.
86+ ///
87+ /// - Parameter maxJitter: The maximum amount of jitter to apply to the ``interval``, which may
88+ /// be applied in either direction.
89+ /// - Returns: A new ``ClientConnectionKeepalive``.
90+ public func jitteringInterval( byAtMost maxJitter: TimeAmount ) -> Self {
91+ var copy = self
92+ copy. jitterInterval ( byAtMost: maxJitter)
93+ return copy
5494 }
5595}
5696
5797public struct ServerConnectionKeepalive : Hashable {
98+ private func checkInvariants( line: UInt = #line) {
99+ precondition ( self . timeout < self . interval, " 'timeout' must be less than 'interval' " , line: line)
100+ }
101+
58102 /// The amount of time to wait before sending a keepalive ping.
59- public var interval : TimeAmount
103+ public var interval : TimeAmount {
104+ didSet { self . checkInvariants ( ) }
105+ }
60106
61107 /// The amount of time to wait for an acknowledgment.
62108 /// If it does not receive an acknowledgment within this time, it will close the connection
63109 /// This value must be less than ``interval``.
64- public var timeout : TimeAmount
110+ public var timeout : TimeAmount {
111+ didSet { self . checkInvariants ( ) }
112+ }
65113
66114 /// Send keepalive pings even if there are no calls in flight.
67115 public var permitWithoutCalls : Bool
@@ -92,13 +140,45 @@ public struct ServerConnectionKeepalive: Hashable {
92140 minimumReceivedPingIntervalWithoutData: TimeAmount = . minutes( 5 ) ,
93141 maximumPingStrikes: UInt = 2
94142 ) {
95- precondition ( timeout < interval, " `timeout` must be less than `interval` " )
96143 self . interval = interval
97144 self . timeout = timeout
98145 self . permitWithoutCalls = permitWithoutCalls
99146 self . maximumPingsWithoutData = maximumPingsWithoutData
100147 self . minimumSentPingIntervalWithoutData = minimumSentPingIntervalWithoutData
101148 self . minimumReceivedPingIntervalWithoutData = minimumReceivedPingIntervalWithoutData
102149 self . maximumPingStrikes = maximumPingStrikes
150+ self . checkInvariants ( )
151+ }
152+ }
153+
154+ extension ServerConnectionKeepalive {
155+ /// Applies jitter to the ``interval``.
156+ ///
157+ /// The current ``interval`` will be adjusted by no more than `maxJitter` in either direction,
158+ /// that is the ``interval`` may increase or decrease by no more than `maxJitter`. As
159+ /// the ``timeout`` must be strictly less than the ``interval``, the lower range of the jittered
160+ /// interval is clamped to `max(interval - maxJitter, timeout + .nanoseconds(1)))`.
161+ ///
162+ /// - Parameter maxJitter: The maximum amount of jitter to apply to the ``interval``, which may
163+ /// be applied in either direction.
164+ public mutating func jitterInterval( byAtMost maxJitter: TimeAmount ) {
165+ // The interval must be larger than the timeout so clamp the lower bound to be greater than
166+ // the timeout.
167+ let lowerBound = max ( self . interval - maxJitter, self . timeout + . nanoseconds( 1 ) )
168+ let upperBound = self . interval + maxJitter
169+ self . interval = . nanoseconds( . random( in: lowerBound. nanoseconds ... upperBound. nanoseconds) )
170+ }
171+
172+ /// Returns a new ``ClientConnectionKeepalive`` with a jittered ``interval``.
173+ ///
174+ /// See also ``jitterInterval(byAtMost:)``.
175+ ///
176+ /// - Parameter maxJitter: The maximum amount of jitter to apply to the ``interval``, which may
177+ /// be applied in either direction.
178+ /// - Returns: A new ``ClientConnectionKeepalive``.
179+ public func jitteringInterval( byAtMost maxJitter: TimeAmount ) -> Self {
180+ var copy = self
181+ copy. jitterInterval ( byAtMost: maxJitter)
182+ return copy
103183 }
104184}
0 commit comments