Skip to content

Commit ae3402e

Browse files
committed
Support http2 keep-alive
Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 93cd8df commit ae3402e

File tree

2 files changed

+37
-3
lines changed

2 files changed

+37
-3
lines changed

src/frequenz/client/base/channel.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ class ChannelOptions:
5151
ssl: SslOptions = SslOptions()
5252
"""SSL options for the channel."""
5353

54+
keep_alive_time_ms: int | None = None
55+
"""The time in milliseconds between HTTP2 keep-alive pings.
56+
57+
If None, keep-alive is disabled."""
58+
59+
keep_alive_timeout_ms: int = 20_000
60+
"""The time in milliseconds to wait for a HTTP2 keep-alive response.
61+
62+
This is only used if `keep_alive_time_ms` is not None."""
63+
5464

5565
def parse_grpc_uri(
5666
uri: str,
@@ -120,6 +130,17 @@ def parse_grpc_uri(
120130
parsed_uri.netloc if parsed_uri.port else f"{parsed_uri.netloc}:{defaults.port}"
121131
)
122132

133+
channel_options = (
134+
[
135+
("grpc.http2.max_pings_without_data", 0),
136+
("grpc.keepalive_permit_without_calls", 1),
137+
("grpc.keepalive_time_ms", defaults.keep_alive_time_ms),
138+
("grpc.keepalive_timeout_ms", defaults.keep_alive_timeout_ms),
139+
]
140+
if defaults.keep_alive_time_ms is not None
141+
else None
142+
)
143+
123144
ssl = defaults.ssl.enabled if options.ssl is None else options.ssl
124145
if ssl:
125146
return secure_channel(
@@ -141,8 +162,9 @@ def parse_grpc_uri(
141162
defaults.ssl.certificate_chain,
142163
),
143164
),
165+
channel_options,
144166
)
145-
return insecure_channel(target)
167+
return insecure_channel(target, channel_options)
146168

147169

148170
def _to_bool(value: str) -> bool:

tests/test_channel.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,16 @@ def test_parse_uri_ok( # pylint: disable=too-many-locals
198198

199199
assert channel == expected_channel
200200
expected_target = f"{expected_host}:{expected_port}"
201+
expected_channel_options = (
202+
[
203+
("grpc.http2.max_pings_without_data", 0),
204+
("grpc.keepalive_permit_without_calls", 1),
205+
("grpc.keepalive_time_ms", defaults.keep_alive_time_ms),
206+
("grpc.keepalive_timeout_ms", defaults.keep_alive_timeout_ms),
207+
]
208+
if defaults.keep_alive_time_ms is not None
209+
else None
210+
)
201211
if expected_ssl:
202212
if isinstance(expected_root_certificates, pathlib.Path):
203213
get_contents_mock.assert_any_call(
@@ -223,10 +233,12 @@ def test_parse_uri_ok( # pylint: disable=too-many-locals
223233
certificate_chain=expected_certificate_chain,
224234
)
225235
secure_channel_mock.assert_called_once_with(
226-
expected_target, expected_credentials
236+
expected_target, expected_credentials, expected_channel_options
227237
)
228238
else:
229-
insecure_channel_mock.assert_called_once_with(expected_target)
239+
insecure_channel_mock.assert_called_once_with(
240+
expected_target, expected_channel_options
241+
)
230242

231243

232244
@pytest.mark.parametrize("value", ["true", "on", "1", "TrUe", "On", "ON", "TRUE"])

0 commit comments

Comments
 (0)