flwr 1.18.0__py3-none-any.whl → 1.20.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flwr/app/__init__.py +15 -0
- flwr/app/error.py +68 -0
- flwr/app/metadata.py +223 -0
- flwr/cli/build.py +94 -59
- flwr/cli/log.py +3 -3
- flwr/cli/login/login.py +3 -7
- flwr/cli/ls.py +15 -36
- flwr/cli/new/new.py +12 -4
- flwr/cli/new/templates/app/README.flowertune.md.tpl +2 -0
- flwr/cli/new/templates/app/README.md.tpl +5 -0
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +1 -1
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +1 -1
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +2 -3
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +25 -17
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +13 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +21 -2
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +19 -2
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +20 -3
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +18 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +18 -1
- flwr/cli/run/run.py +48 -49
- flwr/cli/stop.py +2 -2
- flwr/cli/utils.py +38 -5
- flwr/client/__init__.py +2 -2
- flwr/client/client_app.py +1 -1
- flwr/client/clientapp/__init__.py +0 -7
- flwr/client/grpc_adapter_client/connection.py +15 -8
- flwr/client/grpc_rere_client/connection.py +142 -97
- flwr/client/grpc_rere_client/grpc_adapter.py +34 -6
- flwr/client/message_handler/message_handler.py +1 -1
- flwr/client/mod/comms_mods.py +36 -17
- flwr/client/rest_client/connection.py +176 -103
- flwr/clientapp/__init__.py +15 -0
- flwr/common/__init__.py +2 -2
- flwr/common/auth_plugin/__init__.py +2 -0
- flwr/common/auth_plugin/auth_plugin.py +29 -3
- flwr/common/constant.py +39 -8
- flwr/common/event_log_plugin/event_log_plugin.py +3 -3
- flwr/common/exit/exit_code.py +16 -1
- flwr/common/exit_handlers.py +30 -0
- flwr/common/grpc.py +12 -1
- flwr/common/heartbeat.py +165 -0
- flwr/common/inflatable.py +290 -0
- flwr/common/inflatable_protobuf_utils.py +141 -0
- flwr/common/inflatable_utils.py +508 -0
- flwr/common/message.py +110 -242
- flwr/common/record/__init__.py +2 -1
- flwr/common/record/array.py +402 -0
- flwr/common/record/arraychunk.py +59 -0
- flwr/common/record/arrayrecord.py +103 -225
- flwr/common/record/configrecord.py +59 -4
- flwr/common/record/conversion_utils.py +1 -1
- flwr/common/record/metricrecord.py +55 -4
- flwr/common/record/recorddict.py +69 -1
- flwr/common/recorddict_compat.py +2 -2
- flwr/common/retry_invoker.py +5 -1
- flwr/common/serde.py +59 -211
- flwr/common/serde_utils.py +175 -0
- flwr/common/typing.py +5 -3
- flwr/compat/__init__.py +15 -0
- flwr/compat/client/__init__.py +15 -0
- flwr/{client → compat/client}/app.py +28 -185
- flwr/compat/common/__init__.py +15 -0
- flwr/compat/server/__init__.py +15 -0
- flwr/compat/server/app.py +174 -0
- flwr/compat/simulation/__init__.py +15 -0
- flwr/proto/appio_pb2.py +43 -0
- flwr/proto/appio_pb2.pyi +151 -0
- flwr/proto/appio_pb2_grpc.py +4 -0
- flwr/proto/appio_pb2_grpc.pyi +4 -0
- flwr/proto/clientappio_pb2.py +12 -19
- flwr/proto/clientappio_pb2.pyi +23 -101
- flwr/proto/clientappio_pb2_grpc.py +269 -28
- flwr/proto/clientappio_pb2_grpc.pyi +114 -20
- flwr/proto/fleet_pb2.py +24 -27
- flwr/proto/fleet_pb2.pyi +19 -35
- flwr/proto/fleet_pb2_grpc.py +117 -13
- flwr/proto/fleet_pb2_grpc.pyi +47 -6
- flwr/proto/heartbeat_pb2.py +33 -0
- flwr/proto/heartbeat_pb2.pyi +66 -0
- flwr/proto/heartbeat_pb2_grpc.py +4 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +4 -0
- flwr/proto/message_pb2.py +28 -11
- flwr/proto/message_pb2.pyi +125 -0
- flwr/proto/recorddict_pb2.py +16 -28
- flwr/proto/recorddict_pb2.pyi +46 -64
- flwr/proto/run_pb2.py +24 -32
- flwr/proto/run_pb2.pyi +4 -52
- flwr/proto/serverappio_pb2.py +9 -23
- flwr/proto/serverappio_pb2.pyi +0 -110
- flwr/proto/serverappio_pb2_grpc.py +177 -72
- flwr/proto/serverappio_pb2_grpc.pyi +75 -33
- flwr/proto/simulationio_pb2.py +12 -11
- flwr/proto/simulationio_pb2_grpc.py +35 -0
- flwr/proto/simulationio_pb2_grpc.pyi +14 -0
- flwr/server/__init__.py +1 -1
- flwr/server/app.py +69 -187
- flwr/server/compat/app_utils.py +50 -28
- flwr/server/fleet_event_log_interceptor.py +6 -2
- flwr/server/grid/grpc_grid.py +148 -41
- flwr/server/grid/inmemory_grid.py +5 -4
- flwr/server/serverapp/app.py +45 -17
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +21 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +102 -8
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +2 -5
- flwr/server/superlink/fleet/message_handler/message_handler.py +130 -19
- flwr/server/superlink/fleet/rest_rere/rest_api.py +73 -13
- flwr/server/superlink/fleet/vce/vce_api.py +6 -3
- flwr/server/superlink/linkstate/in_memory_linkstate.py +138 -43
- flwr/server/superlink/linkstate/linkstate.py +53 -20
- flwr/server/superlink/linkstate/sqlite_linkstate.py +149 -55
- flwr/server/superlink/linkstate/utils.py +33 -29
- flwr/server/superlink/serverappio/serverappio_grpc.py +4 -1
- flwr/server/superlink/serverappio/serverappio_servicer.py +230 -84
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +26 -2
- flwr/server/superlink/utils.py +9 -2
- flwr/server/utils/validator.py +2 -2
- flwr/serverapp/__init__.py +15 -0
- flwr/simulation/app.py +25 -0
- flwr/simulation/run_simulation.py +17 -0
- flwr/supercore/__init__.py +15 -0
- flwr/{server/superlink → supercore}/ffs/__init__.py +2 -0
- flwr/{server/superlink → supercore}/ffs/disk_ffs.py +1 -1
- flwr/supercore/grpc_health/__init__.py +22 -0
- flwr/supercore/grpc_health/simple_health_servicer.py +38 -0
- flwr/supercore/license_plugin/__init__.py +22 -0
- flwr/supercore/license_plugin/license_plugin.py +26 -0
- flwr/supercore/object_store/__init__.py +24 -0
- flwr/supercore/object_store/in_memory_object_store.py +229 -0
- flwr/supercore/object_store/object_store.py +170 -0
- flwr/supercore/object_store/object_store_factory.py +44 -0
- flwr/supercore/object_store/utils.py +43 -0
- flwr/supercore/scheduler/__init__.py +22 -0
- flwr/supercore/scheduler/plugin.py +71 -0
- flwr/{client/nodestate/nodestate.py → supercore/utils.py} +14 -13
- flwr/superexec/deployment.py +7 -4
- flwr/superexec/exec_event_log_interceptor.py +8 -4
- flwr/superexec/exec_grpc.py +25 -5
- flwr/superexec/exec_license_interceptor.py +82 -0
- flwr/superexec/exec_servicer.py +135 -24
- flwr/superexec/exec_user_auth_interceptor.py +45 -8
- flwr/superexec/executor.py +5 -1
- flwr/superexec/simulation.py +8 -3
- flwr/superlink/__init__.py +15 -0
- flwr/{client/supernode → supernode}/__init__.py +0 -7
- flwr/supernode/cli/__init__.py +24 -0
- flwr/{client/supernode/app.py → supernode/cli/flower_supernode.py} +3 -19
- flwr/supernode/cli/flwr_clientapp.py +88 -0
- flwr/supernode/nodestate/in_memory_nodestate.py +199 -0
- flwr/supernode/nodestate/nodestate.py +227 -0
- flwr/supernode/runtime/__init__.py +15 -0
- flwr/{client/clientapp/app.py → supernode/runtime/run_clientapp.py} +135 -89
- flwr/supernode/scheduler/__init__.py +22 -0
- flwr/supernode/scheduler/simple_clientapp_scheduler_plugin.py +49 -0
- flwr/supernode/servicer/__init__.py +15 -0
- flwr/supernode/servicer/clientappio/__init__.py +22 -0
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +303 -0
- flwr/supernode/start_client_internal.py +589 -0
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/METADATA +6 -4
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/RECORD +171 -123
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/WHEEL +1 -1
- {flwr-1.18.0.dist-info → flwr-1.20.0.dist-info}/entry_points.txt +2 -2
- flwr/client/clientapp/clientappio_servicer.py +0 -244
- flwr/client/heartbeat.py +0 -74
- flwr/client/nodestate/in_memory_nodestate.py +0 -38
- /flwr/{client → compat/client}/grpc_client/__init__.py +0 -0
- /flwr/{client → compat/client}/grpc_client/connection.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs.py +0 -0
- /flwr/{server/superlink → supercore}/ffs/ffs_factory.py +0 -0
- /flwr/{client → supernode}/nodestate/__init__.py +0 -0
- /flwr/{client → supernode}/nodestate/nodestate_factory.py +0 -0
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
# ==============================================================================
|
|
15
|
-
"""ClientAppIo API servicer."""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from dataclasses import dataclass
|
|
19
|
-
from logging import DEBUG, ERROR
|
|
20
|
-
from typing import Optional, cast
|
|
21
|
-
|
|
22
|
-
import grpc
|
|
23
|
-
|
|
24
|
-
from flwr.common import Context, Message, typing
|
|
25
|
-
from flwr.common.logger import log
|
|
26
|
-
from flwr.common.serde import (
|
|
27
|
-
clientappstatus_to_proto,
|
|
28
|
-
context_from_proto,
|
|
29
|
-
context_to_proto,
|
|
30
|
-
fab_to_proto,
|
|
31
|
-
message_from_proto,
|
|
32
|
-
message_to_proto,
|
|
33
|
-
run_to_proto,
|
|
34
|
-
)
|
|
35
|
-
from flwr.common.typing import Fab, Run
|
|
36
|
-
|
|
37
|
-
# pylint: disable=E0611
|
|
38
|
-
from flwr.proto import clientappio_pb2_grpc
|
|
39
|
-
from flwr.proto.clientappio_pb2 import ( # pylint: disable=E0401
|
|
40
|
-
GetTokenRequest,
|
|
41
|
-
GetTokenResponse,
|
|
42
|
-
PullClientAppInputsRequest,
|
|
43
|
-
PullClientAppInputsResponse,
|
|
44
|
-
PushClientAppOutputsRequest,
|
|
45
|
-
PushClientAppOutputsResponse,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@dataclass
|
|
50
|
-
class ClientAppInputs:
|
|
51
|
-
"""Specify the inputs to the ClientApp."""
|
|
52
|
-
|
|
53
|
-
message: Message
|
|
54
|
-
context: Context
|
|
55
|
-
run: Run
|
|
56
|
-
fab: Optional[Fab]
|
|
57
|
-
token: int
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@dataclass
|
|
61
|
-
class ClientAppOutputs:
|
|
62
|
-
"""Specify the outputs from the ClientApp."""
|
|
63
|
-
|
|
64
|
-
message: Message
|
|
65
|
-
context: Context
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# pylint: disable=C0103,W0613,W0201
|
|
69
|
-
class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
|
|
70
|
-
"""ClientAppIo API servicer."""
|
|
71
|
-
|
|
72
|
-
def __init__(self) -> None:
|
|
73
|
-
self.clientapp_input: Optional[ClientAppInputs] = None
|
|
74
|
-
self.clientapp_output: Optional[ClientAppOutputs] = None
|
|
75
|
-
self.token_returned: bool = False
|
|
76
|
-
self.inputs_returned: bool = False
|
|
77
|
-
|
|
78
|
-
def GetToken(
|
|
79
|
-
self, request: GetTokenRequest, context: grpc.ServicerContext
|
|
80
|
-
) -> GetTokenResponse:
|
|
81
|
-
"""Get token."""
|
|
82
|
-
log(DEBUG, "ClientAppIo.GetToken")
|
|
83
|
-
|
|
84
|
-
# Fail if no ClientAppInputs are available
|
|
85
|
-
if self.clientapp_input is None:
|
|
86
|
-
context.abort(
|
|
87
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
|
88
|
-
"No inputs available.",
|
|
89
|
-
)
|
|
90
|
-
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
|
91
|
-
|
|
92
|
-
# Fail if token was already returned in a previous call
|
|
93
|
-
if self.token_returned:
|
|
94
|
-
context.abort(
|
|
95
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
|
96
|
-
"Token already returned. A token can be returned only once.",
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
# If
|
|
100
|
-
# - ClientAppInputs is set, and
|
|
101
|
-
# - token hasn't been returned before,
|
|
102
|
-
# return token
|
|
103
|
-
self.token_returned = True
|
|
104
|
-
return GetTokenResponse(token=clientapp_input.token)
|
|
105
|
-
|
|
106
|
-
def PullClientAppInputs(
|
|
107
|
-
self, request: PullClientAppInputsRequest, context: grpc.ServicerContext
|
|
108
|
-
) -> PullClientAppInputsResponse:
|
|
109
|
-
"""Pull Message, Context, and Run."""
|
|
110
|
-
log(DEBUG, "ClientAppIo.PullClientAppInputs")
|
|
111
|
-
|
|
112
|
-
# Fail if no ClientAppInputs are available
|
|
113
|
-
if self.clientapp_input is None:
|
|
114
|
-
context.abort(
|
|
115
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
|
116
|
-
"No inputs available.",
|
|
117
|
-
)
|
|
118
|
-
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
|
119
|
-
|
|
120
|
-
# Fail if token wasn't returned in a previous call
|
|
121
|
-
if not self.token_returned:
|
|
122
|
-
context.abort(
|
|
123
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
|
124
|
-
"Token hasn't been returned."
|
|
125
|
-
"Token must be returned before can be returned only once.",
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
# Fail if token isn't matching
|
|
129
|
-
if request.token != clientapp_input.token:
|
|
130
|
-
context.abort(
|
|
131
|
-
grpc.StatusCode.INVALID_ARGUMENT,
|
|
132
|
-
"Mismatch between ClientApp and SuperNode token",
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
# Success
|
|
136
|
-
self.inputs_returned = True
|
|
137
|
-
return PullClientAppInputsResponse(
|
|
138
|
-
message=message_to_proto(clientapp_input.message),
|
|
139
|
-
context=context_to_proto(clientapp_input.context),
|
|
140
|
-
run=run_to_proto(clientapp_input.run),
|
|
141
|
-
fab=fab_to_proto(clientapp_input.fab) if clientapp_input.fab else None,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
def PushClientAppOutputs(
|
|
145
|
-
self, request: PushClientAppOutputsRequest, context: grpc.ServicerContext
|
|
146
|
-
) -> PushClientAppOutputsResponse:
|
|
147
|
-
"""Push Message and Context."""
|
|
148
|
-
log(DEBUG, "ClientAppIo.PushClientAppOutputs")
|
|
149
|
-
|
|
150
|
-
# Fail if no ClientAppInputs are available
|
|
151
|
-
if not self.clientapp_input:
|
|
152
|
-
context.abort(
|
|
153
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
|
154
|
-
"No inputs available.",
|
|
155
|
-
)
|
|
156
|
-
clientapp_input = cast(ClientAppInputs, self.clientapp_input)
|
|
157
|
-
|
|
158
|
-
# Fail if token wasn't returned in a previous call
|
|
159
|
-
if not self.token_returned:
|
|
160
|
-
context.abort(
|
|
161
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
|
162
|
-
"Token hasn't been returned."
|
|
163
|
-
"Token must be returned before can be returned only once.",
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
# Fail if inputs weren't delivered in a previous call
|
|
167
|
-
if not self.inputs_returned:
|
|
168
|
-
context.abort(
|
|
169
|
-
grpc.StatusCode.FAILED_PRECONDITION,
|
|
170
|
-
"Inputs haven't been delivered."
|
|
171
|
-
"Inputs must be delivered before can be returned only once.",
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
# Fail if token isn't matching
|
|
175
|
-
if request.token != clientapp_input.token:
|
|
176
|
-
context.abort(
|
|
177
|
-
grpc.StatusCode.INVALID_ARGUMENT,
|
|
178
|
-
"Mismatch between ClientApp and SuperNode token",
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
# Preconditions met
|
|
182
|
-
try:
|
|
183
|
-
# Update Message and Context
|
|
184
|
-
self.clientapp_output = ClientAppOutputs(
|
|
185
|
-
message=message_from_proto(request.message),
|
|
186
|
-
context=context_from_proto(request.context),
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
# Set status
|
|
190
|
-
code = typing.ClientAppOutputCode.SUCCESS
|
|
191
|
-
status = typing.ClientAppOutputStatus(code=code, message="Success")
|
|
192
|
-
except Exception as e: # pylint: disable=broad-exception-caught
|
|
193
|
-
log(ERROR, "ClientApp failed to push message to SuperNode, %s", e)
|
|
194
|
-
code = typing.ClientAppOutputCode.UNKNOWN_ERROR
|
|
195
|
-
status = typing.ClientAppOutputStatus(code=code, message="Unkonwn error")
|
|
196
|
-
|
|
197
|
-
# Return status to ClientApp process
|
|
198
|
-
proto_status = clientappstatus_to_proto(status=status)
|
|
199
|
-
return PushClientAppOutputsResponse(status=proto_status)
|
|
200
|
-
|
|
201
|
-
def set_inputs(
|
|
202
|
-
self, clientapp_input: ClientAppInputs, token_returned: bool
|
|
203
|
-
) -> None:
|
|
204
|
-
"""Set ClientApp inputs.
|
|
205
|
-
|
|
206
|
-
Parameters
|
|
207
|
-
----------
|
|
208
|
-
clientapp_input : ClientAppInputs
|
|
209
|
-
The inputs to the ClientApp.
|
|
210
|
-
token_returned : bool
|
|
211
|
-
A boolean indicating if the token has been returned.
|
|
212
|
-
Set to `True` when passing the token to `flwr-clientap`
|
|
213
|
-
and `False` otherwise.
|
|
214
|
-
"""
|
|
215
|
-
if (
|
|
216
|
-
self.clientapp_input is not None
|
|
217
|
-
or self.clientapp_output is not None
|
|
218
|
-
or self.token_returned
|
|
219
|
-
):
|
|
220
|
-
raise ValueError(
|
|
221
|
-
"ClientAppInputs and ClientAppOutputs must not be set before "
|
|
222
|
-
"calling `set_inputs`."
|
|
223
|
-
)
|
|
224
|
-
log(DEBUG, "ClientAppInputs set (token: %s)", clientapp_input.token)
|
|
225
|
-
self.clientapp_input = clientapp_input
|
|
226
|
-
self.token_returned = token_returned
|
|
227
|
-
|
|
228
|
-
def has_outputs(self) -> bool:
|
|
229
|
-
"""Check if ClientAppOutputs are available."""
|
|
230
|
-
return self.clientapp_output is not None
|
|
231
|
-
|
|
232
|
-
def get_outputs(self) -> ClientAppOutputs:
|
|
233
|
-
"""Get ClientApp outputs."""
|
|
234
|
-
if self.clientapp_output is None:
|
|
235
|
-
raise ValueError("ClientAppOutputs not set before calling `get_outputs`.")
|
|
236
|
-
|
|
237
|
-
# Set outputs to a local variable and clear state
|
|
238
|
-
output: ClientAppOutputs = self.clientapp_output
|
|
239
|
-
self.clientapp_input = None
|
|
240
|
-
self.clientapp_output = None
|
|
241
|
-
self.token_returned = False
|
|
242
|
-
self.inputs_returned = False
|
|
243
|
-
|
|
244
|
-
return output
|
flwr/client/heartbeat.py
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
# ==============================================================================
|
|
15
|
-
"""Heartbeat utility functions."""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import threading
|
|
19
|
-
from typing import Callable
|
|
20
|
-
|
|
21
|
-
import grpc
|
|
22
|
-
|
|
23
|
-
from flwr.common.constant import PING_CALL_TIMEOUT
|
|
24
|
-
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def _ping_loop(ping_fn: Callable[[], None], stop_event: threading.Event) -> None:
|
|
28
|
-
def wait_fn(wait_time: float) -> None:
|
|
29
|
-
if not stop_event.is_set():
|
|
30
|
-
stop_event.wait(wait_time)
|
|
31
|
-
|
|
32
|
-
def on_backoff(state: RetryState) -> None:
|
|
33
|
-
err = state.exception
|
|
34
|
-
if not isinstance(err, grpc.RpcError):
|
|
35
|
-
return
|
|
36
|
-
status_code = err.code()
|
|
37
|
-
# If ping call timeout is triggered
|
|
38
|
-
if status_code == grpc.StatusCode.DEADLINE_EXCEEDED:
|
|
39
|
-
# Avoid long wait time.
|
|
40
|
-
if state.actual_wait is None:
|
|
41
|
-
return
|
|
42
|
-
state.actual_wait = max(state.actual_wait - PING_CALL_TIMEOUT, 0.0)
|
|
43
|
-
|
|
44
|
-
def wrapped_ping() -> None:
|
|
45
|
-
if not stop_event.is_set():
|
|
46
|
-
ping_fn()
|
|
47
|
-
|
|
48
|
-
retrier = RetryInvoker(
|
|
49
|
-
exponential,
|
|
50
|
-
grpc.RpcError,
|
|
51
|
-
max_tries=None,
|
|
52
|
-
max_time=None,
|
|
53
|
-
on_backoff=on_backoff,
|
|
54
|
-
wait_function=wait_fn,
|
|
55
|
-
)
|
|
56
|
-
while not stop_event.is_set():
|
|
57
|
-
retrier.invoke(wrapped_ping)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def start_ping_loop(
|
|
61
|
-
ping_fn: Callable[[], None], stop_event: threading.Event
|
|
62
|
-
) -> threading.Thread:
|
|
63
|
-
"""Start a ping loop in a separate thread.
|
|
64
|
-
|
|
65
|
-
This function initializes a new thread that runs a ping loop, allowing for
|
|
66
|
-
asynchronous ping operations. The loop can be terminated through the provided stop
|
|
67
|
-
event.
|
|
68
|
-
"""
|
|
69
|
-
thread = threading.Thread(
|
|
70
|
-
target=_ping_loop, args=(ping_fn, stop_event), daemon=True
|
|
71
|
-
)
|
|
72
|
-
thread.start()
|
|
73
|
-
|
|
74
|
-
return thread
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
# ==============================================================================
|
|
15
|
-
"""In-memory NodeState implementation."""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from typing import Optional
|
|
19
|
-
|
|
20
|
-
from flwr.client.nodestate.nodestate import NodeState
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class InMemoryNodeState(NodeState):
|
|
24
|
-
"""In-memory NodeState implementation."""
|
|
25
|
-
|
|
26
|
-
def __init__(self) -> None:
|
|
27
|
-
# Store node_id
|
|
28
|
-
self.node_id: Optional[int] = None
|
|
29
|
-
|
|
30
|
-
def set_node_id(self, node_id: Optional[int]) -> None:
|
|
31
|
-
"""Set the node ID."""
|
|
32
|
-
self.node_id = node_id
|
|
33
|
-
|
|
34
|
-
def get_node_id(self) -> int:
|
|
35
|
-
"""Get the node ID."""
|
|
36
|
-
if self.node_id is None:
|
|
37
|
-
raise ValueError("Node ID not set")
|
|
38
|
-
return self.node_id
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|