flwr-nightly 1.19.0.dev20250602__py3-none-any.whl → 1.19.0.dev20250604__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.
@@ -14,11 +14,10 @@
14
14
  # ==============================================================================
15
15
  """Contextmanager for a gRPC request-response channel to the Flower server."""
16
16
 
17
-
18
17
  from collections.abc import Iterator, Sequence
19
18
  from contextlib import contextmanager
20
19
  from copy import copy
21
- from logging import ERROR
20
+ from logging import DEBUG, ERROR
22
21
  from pathlib import Path
23
22
  from typing import Callable, Optional, Union, cast
24
23
 
@@ -31,13 +30,17 @@ from flwr.common import GRPC_MAX_MESSAGE_LENGTH
31
30
  from flwr.common.constant import HEARTBEAT_CALL_TIMEOUT, HEARTBEAT_DEFAULT_INTERVAL
32
31
  from flwr.common.grpc import create_channel, on_channel_state_change
33
32
  from flwr.common.heartbeat import HeartbeatSender
33
+ from flwr.common.inflatable_grpc_utils import (
34
+ pull_object_from_servicer,
35
+ push_object_to_servicer,
36
+ )
34
37
  from flwr.common.logger import log
35
- from flwr.common.message import Message
36
- from flwr.common.retry_invoker import RetryInvoker
38
+ from flwr.common.message import Message, get_message_to_descendant_id_mapping
39
+ from flwr.common.retry_invoker import RetryInvoker, _wrap_stub
37
40
  from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
38
41
  generate_key_pairs,
39
42
  )
40
- from flwr.common.serde import message_from_proto, message_to_proto, run_from_proto
43
+ from flwr.common.serde import message_to_proto, run_from_proto
41
44
  from flwr.common.typing import Fab, Run, RunNotRunningException
42
45
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
43
46
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
@@ -46,6 +49,7 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
46
49
  PullMessagesRequest,
47
50
  PullMessagesResponse,
48
51
  PushMessagesRequest,
52
+ PushMessagesResponse,
49
53
  )
50
54
  from flwr.proto.fleet_pb2_grpc import FleetStub # pylint: disable=E0611
51
55
  from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
@@ -159,6 +163,8 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
159
163
  # If the status code is PERMISSION_DENIED, additionally raise RunNotRunningException
160
164
  retry_invoker.should_giveup = _should_giveup_fn
161
165
 
166
+ # Wrap stub
167
+ _wrap_stub(stub, retry_invoker)
162
168
  ###########################################################################
163
169
  # send_node_heartbeat/create_node/delete_node/receive/send/get_run functions
164
170
  ###########################################################################
@@ -203,10 +209,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
203
209
  create_node_request = CreateNodeRequest(
204
210
  heartbeat_interval=HEARTBEAT_DEFAULT_INTERVAL
205
211
  )
206
- create_node_response = retry_invoker.invoke(
207
- stub.CreateNode,
208
- request=create_node_request,
209
- )
212
+ create_node_response = stub.CreateNode(request=create_node_request)
210
213
 
211
214
  # Remember the node and start the heartbeat sender
212
215
  nonlocal node
@@ -227,7 +230,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
227
230
 
228
231
  # Call FleetAPI
229
232
  delete_node_request = DeleteNodeRequest(node=node)
230
- retry_invoker.invoke(stub.DeleteNode, request=delete_node_request)
233
+ stub.DeleteNode(request=delete_node_request)
231
234
 
232
235
  # Cleanup
233
236
  node = None
@@ -241,9 +244,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
241
244
 
242
245
  # Request instructions (message) from server
243
246
  request = PullMessagesRequest(node=node)
244
- response: PullMessagesResponse = retry_invoker.invoke(
245
- stub.PullMessages, request=request
246
- )
247
+ response: PullMessagesResponse = stub.PullMessages(request=request)
247
248
 
248
249
  # Get the current Messages
249
250
  message_proto = (
@@ -257,7 +258,24 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
257
258
  message_proto = None
258
259
 
259
260
  # Construct the Message
260
- in_message = message_from_proto(message_proto) if message_proto else None
261
+ in_message: Optional[Message] = None
262
+
263
+ if message_proto:
264
+ in_message = cast(
265
+ Message,
266
+ pull_object_from_servicer(
267
+ object_id=message_proto.metadata.message_id,
268
+ stub=stub,
269
+ node=node,
270
+ run_id=message_proto.metadata.run_id,
271
+ ),
272
+ )
273
+
274
+ if in_message:
275
+ # The deflated message doesn't contain the message_id (its own object_id)
276
+ # Inject
277
+ # pylint: disable-next=W0212
278
+ in_message.metadata._message_id = message_proto.metadata.message_id # type: ignore
261
279
 
262
280
  # Remember `metadata` of the in message
263
281
  nonlocal metadata
@@ -288,8 +306,24 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
288
306
 
289
307
  # Serialize Message
290
308
  message_proto = message_to_proto(message=message)
291
- request = PushMessagesRequest(node=node, messages_list=[message_proto])
292
- _ = retry_invoker.invoke(stub.PushMessages, request)
309
+ descendants_mapping = get_message_to_descendant_id_mapping(message)
310
+ request = PushMessagesRequest(
311
+ node=node,
312
+ messages_list=[message_proto],
313
+ msg_to_descendant_mapping=descendants_mapping,
314
+ )
315
+ response: PushMessagesResponse = stub.PushMessages(request=request)
316
+
317
+ if response.objects_to_push:
318
+ objs_to_push = set(response.objects_to_push[message.object_id].object_ids)
319
+ push_object_to_servicer(
320
+ message,
321
+ stub,
322
+ node,
323
+ run_id=message.metadata.run_id,
324
+ object_ids_to_push=objs_to_push,
325
+ )
326
+ log(DEBUG, "Pushed %s objects to servicer.", len(objs_to_push))
293
327
 
294
328
  # Cleanup
295
329
  metadata = None
@@ -297,10 +331,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
297
331
  def get_run(run_id: int) -> Run:
298
332
  # Call FleetAPI
299
333
  get_run_request = GetRunRequest(node=node, run_id=run_id)
300
- get_run_response: GetRunResponse = retry_invoker.invoke(
301
- stub.GetRun,
302
- request=get_run_request,
303
- )
334
+ get_run_response: GetRunResponse = stub.GetRun(request=get_run_request)
304
335
 
305
336
  # Return fab_id and fab_version
306
337
  return run_from_proto(get_run_response.run)
@@ -308,10 +339,7 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
308
339
  def get_fab(fab_hash: str, run_id: int) -> Fab:
309
340
  # Call FleetAPI
310
341
  get_fab_request = GetFabRequest(node=node, hash_str=fab_hash, run_id=run_id)
311
- get_fab_response: GetFabResponse = retry_invoker.invoke(
312
- stub.GetFab,
313
- request=get_fab_request,
314
- )
342
+ get_fab_response: GetFabResponse = stub.GetFab(request=get_fab_request)
315
343
 
316
344
  return Fab(get_fab_response.fab.hash_str, get_fab_response.fab.content)
317
345
 
flwr/common/inflatable.py CHANGED
@@ -18,9 +18,11 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import hashlib
21
+ from logging import ERROR
21
22
  from typing import TypeVar, cast
22
23
 
23
24
  from .constant import HEAD_BODY_DIVIDER, HEAD_VALUE_DIVIDER
25
+ from .logger import log
24
26
 
25
27
 
26
28
  class InflatableObject:
@@ -163,8 +165,12 @@ def get_object_body_len_from_object_content(object_content: bytes) -> int:
163
165
 
164
166
  def check_body_len_consistency(object_content: bytes) -> bool:
165
167
  """Check that the object body is of length as specified in the head."""
166
- body_len = get_object_body_len_from_object_content(object_content)
167
- return body_len == len(_get_object_body(object_content))
168
+ try:
169
+ body_len = get_object_body_len_from_object_content(object_content)
170
+ return body_len == len(_get_object_body(object_content))
171
+ except ValueError:
172
+ log(ERROR, "Object content does match the expected format.")
173
+ return False
168
174
 
169
175
 
170
176
  def get_object_head_values_from_object_content(
@@ -15,8 +15,10 @@
15
15
  """InflatableObject utils."""
16
16
 
17
17
 
18
+ from time import sleep
18
19
  from typing import Optional, Union
19
20
 
21
+ from flwr.client.grpc_rere_client.grpc_adapter import GrpcAdapter
20
22
  from flwr.proto.fleet_pb2_grpc import FleetStub # pylint: disable=E0611
21
23
  from flwr.proto.message_pb2 import ( # pylint: disable=E0611
22
24
  PullObjectRequest,
@@ -48,7 +50,7 @@ inflatable_class_registry: dict[str, type[InflatableObject]] = {
48
50
 
49
51
  def push_object_to_servicer(
50
52
  obj: InflatableObject,
51
- stub: Union[FleetStub, ServerAppIoStub],
53
+ stub: Union[FleetStub, ServerAppIoStub, GrpcAdapter],
52
54
  node: Node,
53
55
  run_id: int,
54
56
  object_ids_to_push: Optional[set[str]] = None,
@@ -87,16 +89,20 @@ def push_object_to_servicer(
87
89
 
88
90
  def pull_object_from_servicer(
89
91
  object_id: str,
90
- stub: Union[FleetStub, ServerAppIoStub],
92
+ stub: Union[FleetStub, ServerAppIoStub, GrpcAdapter],
91
93
  node: Node,
92
94
  run_id: int,
93
95
  ) -> InflatableObject:
94
96
  """Recursively inflate an object by pulling it from the servicer."""
95
97
  # Pull object
96
- object_proto: PullObjectResponse = stub.PullObject(
97
- PullObjectRequest(node=node, run_id=run_id, object_id=object_id)
98
- )
99
- object_content = object_proto.object_content
98
+ object_available = False
99
+ while not object_available:
100
+ object_proto: PullObjectResponse = stub.PullObject(
101
+ PullObjectRequest(node=node, run_id=run_id, object_id=object_id)
102
+ )
103
+ object_available = object_proto.object_available
104
+ object_content = object_proto.object_content
105
+ sleep(0.1)
100
106
 
101
107
  # Extract object class and object_ids of children
102
108
  obj_type, children_obj_ids, _ = get_object_head_values_from_object_content(
@@ -180,7 +180,7 @@ class MetricRecord(TypedDict[str, MetricRecordValues], InflatableObject):
180
180
  MetricRecord
181
181
  The inflated MetricRecord.
182
182
  """
183
- if children is not None:
183
+ if children:
184
184
  raise ValueError("`MetricRecord` objects do not have children.")
185
185
 
186
186
  obj_body = get_object_body(object_content, cls)
@@ -25,10 +25,12 @@ from typing import Any, Callable, Optional, Union, cast
25
25
 
26
26
  import grpc
27
27
 
28
+ from flwr.client.grpc_rere_client.grpc_adapter import GrpcAdapter
28
29
  from flwr.common.constant import MAX_RETRY_DELAY
29
30
  from flwr.common.logger import log
30
31
  from flwr.common.typing import RunNotRunningException
31
32
  from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
33
+ from flwr.proto.fleet_pb2_grpc import FleetStub
32
34
  from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
33
35
  from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
34
36
 
@@ -366,7 +368,9 @@ def _make_simple_grpc_retry_invoker() -> RetryInvoker:
366
368
 
367
369
 
368
370
  def _wrap_stub(
369
- stub: Union[ServerAppIoStub, ClientAppIoStub, SimulationIoStub],
371
+ stub: Union[
372
+ ServerAppIoStub, ClientAppIoStub, SimulationIoStub, FleetStub, GrpcAdapter
373
+ ],
370
374
  retry_invoker: RetryInvoker,
371
375
  ) -> None:
372
376
  """Wrap a gRPC stub with a retry invoker."""
flwr/proto/message_pb2.py CHANGED
@@ -18,7 +18,7 @@ from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2
18
18
  from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2
19
19
 
20
20
 
21
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/message.proto\x12\nflwr.proto\x1a\x16\x66lwr/proto/error.proto\x1a\x1b\x66lwr/proto/recorddict.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x15\x66lwr/proto/node.proto\"|\n\x07Message\x12&\n\x08metadata\x18\x01 \x01(\x0b\x32\x14.flwr.proto.Metadata\x12\'\n\x07\x63ontent\x18\x02 \x01(\x0b\x32\x16.flwr.proto.RecordDict\x12 \n\x05\x65rror\x18\x03 \x01(\x0b\x32\x11.flwr.proto.Error\"\xd0\x02\n\x07\x43ontext\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x0f\n\x07node_id\x18\x02 \x01(\x04\x12\x38\n\x0bnode_config\x18\x03 \x03(\x0b\x32#.flwr.proto.Context.NodeConfigEntry\x12%\n\x05state\x18\x04 \x01(\x0b\x32\x16.flwr.proto.RecordDict\x12\x36\n\nrun_config\x18\x05 \x03(\x0b\x32\".flwr.proto.Context.RunConfigEntry\x1a\x45\n\x0fNodeConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\x1a\x44\n\x0eRunConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\xbe\x01\n\x08Metadata\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x12\n\nmessage_id\x18\x02 \x01(\t\x12\x13\n\x0bsrc_node_id\x18\x03 \x01(\x04\x12\x13\n\x0b\x64st_node_id\x18\x04 \x01(\x04\x12\x1b\n\x13reply_to_message_id\x18\x05 \x01(\t\x12\x10\n\x08group_id\x18\x06 \x01(\t\x12\x0b\n\x03ttl\x18\x07 \x01(\x01\x12\x14\n\x0cmessage_type\x18\x08 \x01(\t\x12\x12\n\ncreated_at\x18\t \x01(\x01\"\x1f\n\tObjectIDs\x12\x12\n\nobject_ids\x18\x01 \x03(\t\"n\n\x11PushObjectRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x0e\n\x06run_id\x18\x02 \x01(\x04\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12\x16\n\x0eobject_content\x18\x04 \x01(\x0c\"$\n\x12PushObjectResponse\x12\x0e\n\x06stored\x18\x01 \x01(\x08\"V\n\x11PullObjectRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x0e\n\x06run_id\x18\x02 \x01(\x04\x12\x11\n\tobject_id\x18\x03 \x01(\t\",\n\x12PullObjectResponse\x12\x16\n\x0eobject_content\x18\x01 \x01(\x0c\x62\x06proto3')
21
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/message.proto\x12\nflwr.proto\x1a\x16\x66lwr/proto/error.proto\x1a\x1b\x66lwr/proto/recorddict.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x15\x66lwr/proto/node.proto\"|\n\x07Message\x12&\n\x08metadata\x18\x01 \x01(\x0b\x32\x14.flwr.proto.Metadata\x12\'\n\x07\x63ontent\x18\x02 \x01(\x0b\x32\x16.flwr.proto.RecordDict\x12 \n\x05\x65rror\x18\x03 \x01(\x0b\x32\x11.flwr.proto.Error\"\xd0\x02\n\x07\x43ontext\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x0f\n\x07node_id\x18\x02 \x01(\x04\x12\x38\n\x0bnode_config\x18\x03 \x03(\x0b\x32#.flwr.proto.Context.NodeConfigEntry\x12%\n\x05state\x18\x04 \x01(\x0b\x32\x16.flwr.proto.RecordDict\x12\x36\n\nrun_config\x18\x05 \x03(\x0b\x32\".flwr.proto.Context.RunConfigEntry\x1a\x45\n\x0fNodeConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\x1a\x44\n\x0eRunConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\xbe\x01\n\x08Metadata\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x12\n\nmessage_id\x18\x02 \x01(\t\x12\x13\n\x0bsrc_node_id\x18\x03 \x01(\x04\x12\x13\n\x0b\x64st_node_id\x18\x04 \x01(\x04\x12\x1b\n\x13reply_to_message_id\x18\x05 \x01(\t\x12\x10\n\x08group_id\x18\x06 \x01(\t\x12\x0b\n\x03ttl\x18\x07 \x01(\x01\x12\x14\n\x0cmessage_type\x18\x08 \x01(\t\x12\x12\n\ncreated_at\x18\t \x01(\x01\"\x1f\n\tObjectIDs\x12\x12\n\nobject_ids\x18\x01 \x03(\t\"n\n\x11PushObjectRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x0e\n\x06run_id\x18\x02 \x01(\x04\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12\x16\n\x0eobject_content\x18\x04 \x01(\x0c\"$\n\x12PushObjectResponse\x12\x0e\n\x06stored\x18\x01 \x01(\x08\"V\n\x11PullObjectRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x0e\n\x06run_id\x18\x02 \x01(\x04\x12\x11\n\tobject_id\x18\x03 \x01(\t\"\\\n\x12PullObjectResponse\x12\x14\n\x0cobject_found\x18\x01 \x01(\x08\x12\x18\n\x10object_available\x18\x02 \x01(\x08\x12\x16\n\x0eobject_content\x18\x03 \x01(\x0c\x62\x06proto3')
22
22
 
23
23
  _globals = globals()
24
24
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -48,5 +48,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
48
48
  _globals['_PULLOBJECTREQUEST']._serialized_start=985
49
49
  _globals['_PULLOBJECTREQUEST']._serialized_end=1071
50
50
  _globals['_PULLOBJECTRESPONSE']._serialized_start=1073
51
- _globals['_PULLOBJECTRESPONSE']._serialized_end=1117
51
+ _globals['_PULLOBJECTRESPONSE']._serialized_end=1165
52
52
  # @@protoc_insertion_point(module_scope)
@@ -196,11 +196,17 @@ global___PullObjectRequest = PullObjectRequest
196
196
 
197
197
  class PullObjectResponse(google.protobuf.message.Message):
198
198
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
199
+ OBJECT_FOUND_FIELD_NUMBER: builtins.int
200
+ OBJECT_AVAILABLE_FIELD_NUMBER: builtins.int
199
201
  OBJECT_CONTENT_FIELD_NUMBER: builtins.int
202
+ object_found: builtins.bool
203
+ object_available: builtins.bool
200
204
  object_content: builtins.bytes
201
205
  def __init__(self,
202
206
  *,
207
+ object_found: builtins.bool = ...,
208
+ object_available: builtins.bool = ...,
203
209
  object_content: builtins.bytes = ...,
204
210
  ) -> None: ...
205
- def ClearField(self, field_name: typing_extensions.Literal["object_content",b"object_content"]) -> None: ...
211
+ def ClearField(self, field_name: typing_extensions.Literal["object_available",b"object_available","object_content",b"object_content","object_found",b"object_found"]) -> None: ...
206
212
  global___PullObjectResponse = PullObjectResponse
@@ -28,11 +28,15 @@ from flwr.common.constant import (
28
28
  SUPERLINK_NODE_ID,
29
29
  )
30
30
  from flwr.common.grpc import create_channel, on_channel_state_change
31
+ from flwr.common.inflatable_grpc_utils import (
32
+ pull_object_from_servicer,
33
+ push_object_to_servicer,
34
+ )
31
35
  from flwr.common.logger import log, warn_deprecated_feature
36
+ from flwr.common.message import get_message_to_descendant_id_mapping
32
37
  from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
33
- from flwr.common.serde import message_from_proto, message_to_proto, run_from_proto
38
+ from flwr.common.serde import message_to_proto, run_from_proto
34
39
  from flwr.common.typing import Run
35
- from flwr.proto.message_pb2 import Message as ProtoMessage # pylint: disable=E0611
36
40
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
37
41
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
38
42
  from flwr.proto.serverappio_pb2 import ( # pylint: disable=E0611
@@ -198,6 +202,35 @@ class GrpcGrid(Grid):
198
202
  )
199
203
  return [node.node_id for node in res.nodes]
200
204
 
205
+ def _try_push_message(self, run_id: int, message: Message) -> str:
206
+ """Push one message and its associated objects."""
207
+ # Compute mapping of message descendants
208
+ descendants_mapping = get_message_to_descendant_id_mapping(message)
209
+
210
+ # Call GrpcServerAppIoStub method
211
+ res: PushInsMessagesResponse = self._stub.PushMessages(
212
+ PushInsMessagesRequest(
213
+ messages_list=[message_to_proto(message)],
214
+ run_id=run_id,
215
+ msg_to_descendant_mapping=descendants_mapping,
216
+ )
217
+ )
218
+
219
+ # Push objects
220
+ msg_id = res.message_ids[0]
221
+ # If Message was added to the LinkState correctly
222
+ if msg_id is not None:
223
+ obj_ids_to_push = set(res.objects_to_push[msg_id].object_ids)
224
+ # Push only object that are not in the store
225
+ push_object_to_servicer(
226
+ message,
227
+ self._stub,
228
+ node=self.node,
229
+ run_id=run_id,
230
+ object_ids_to_push=obj_ids_to_push,
231
+ )
232
+ return msg_id
233
+
201
234
  def push_messages(self, messages: Iterable[Message]) -> Iterable[str]:
202
235
  """Push messages to specified node IDs.
203
236
 
@@ -206,58 +239,64 @@ class GrpcGrid(Grid):
206
239
  """
207
240
  # Construct Messages
208
241
  run_id = cast(Run, self._run).run_id
209
- message_proto_list: list[ProtoMessage] = []
210
- for msg in messages:
211
- # Populate metadata
212
- msg.metadata.__dict__["_run_id"] = run_id
213
- msg.metadata.__dict__["_src_node_id"] = self.node.node_id
214
- msg.metadata.__dict__["_message_id"] = msg.object_id
215
- # Check message
216
- self._check_message(msg)
217
- # Convert to proto
218
- msg_proto = message_to_proto(msg)
219
- # Add to list
220
- message_proto_list.append(msg_proto)
221
-
242
+ message_ids: list[str] = []
222
243
  try:
223
- # Call GrpcServerAppIoStub method
224
- res: PushInsMessagesResponse = self._stub.PushMessages(
225
- PushInsMessagesRequest(messages_list=message_proto_list, run_id=run_id)
226
- )
227
- if len([msg_id for msg_id in res.message_ids if msg_id]) != len(
228
- message_proto_list
229
- ):
230
- log(
231
- WARNING,
232
- "Not all messages could be pushed to the SuperLink. The returned "
233
- "list has `None` for those messages (the order is preserved as "
234
- "passed to `push_messages`). This could be due to a malformed "
235
- "message.",
236
- )
237
- return list(res.message_ids)
244
+ for msg in messages:
245
+ # Populate metadata
246
+ msg.metadata.__dict__["_run_id"] = run_id
247
+ msg.metadata.__dict__["_src_node_id"] = self.node.node_id
248
+ msg.metadata.__dict__["_message_id"] = msg.object_id
249
+ # Check message
250
+ self._check_message(msg)
251
+ # Try pushing message and its objects
252
+ message_ids.append(self._try_push_message(run_id, msg))
253
+
238
254
  except grpc.RpcError as e:
239
255
  if e.code() == grpc.StatusCode.RESOURCE_EXHAUSTED: # pylint: disable=E1101
240
256
  log(ERROR, ERROR_MESSAGE_PUSH_MESSAGES_RESOURCE_EXHAUSTED)
241
257
  return []
242
258
  raise
243
259
 
260
+ if None in message_ids:
261
+ log(
262
+ WARNING,
263
+ "Not all messages could be pushed to the SuperLink. The returned "
264
+ "list has `None` for those messages (the order is preserved as "
265
+ "passed to `push_messages`). This could be due to a malformed "
266
+ "message.",
267
+ )
268
+
269
+ return message_ids
270
+
244
271
  def pull_messages(self, message_ids: Iterable[str]) -> Iterable[Message]:
245
272
  """Pull messages based on message IDs.
246
273
 
247
274
  This method is used to collect messages from the SuperLink that correspond to a
248
275
  set of given message IDs.
249
276
  """
277
+ run_id = cast(Run, self._run).run_id
250
278
  try:
251
279
  # Pull Messages
252
280
  res: PullResMessagesResponse = self._stub.PullMessages(
253
281
  PullResMessagesRequest(
254
282
  message_ids=message_ids,
255
- run_id=cast(Run, self._run).run_id,
283
+ run_id=run_id,
256
284
  )
257
285
  )
258
- # Convert Message from Protobuf representation
259
- msgs = [message_from_proto(msg_proto) for msg_proto in res.messages_list]
260
- return msgs
286
+ # Pull Messages from store
287
+ inflated_msgs: list[Message] = []
288
+ for msg_proto in res.messages_list:
289
+
290
+ message = pull_object_from_servicer(
291
+ msg_proto.metadata.message_id,
292
+ self._stub,
293
+ node=self.node,
294
+ run_id=run_id,
295
+ )
296
+ inflated_msgs.append(cast(Message, message))
297
+
298
+ return inflated_msgs
299
+
261
300
  except grpc.RpcError as e:
262
301
  if e.code() == grpc.StatusCode.RESOURCE_EXHAUSTED: # pylint: disable=E1101
263
302
  log(ERROR, ERROR_MESSAGE_PULL_MESSAGES_RESOURCE_EXHAUSTED)
@@ -17,8 +17,10 @@
17
17
 
18
18
  from .disk_ffs import DiskFfs as DiskFfs
19
19
  from .ffs import Ffs as Ffs
20
+ from .ffs_factory import FfsFactory as FfsFactory
20
21
 
21
22
  __all__ = [
22
23
  "DiskFfs",
23
24
  "Ffs",
25
+ "FfsFactory",
24
26
  ]
@@ -15,11 +15,12 @@
15
15
  """Fleet API gRPC request-response servicer."""
16
16
 
17
17
 
18
- from logging import DEBUG, INFO
18
+ from logging import DEBUG, ERROR, INFO
19
19
 
20
20
  import grpc
21
21
  from google.protobuf.json_format import MessageToDict
22
22
 
23
+ from flwr.common.constant import Status
23
24
  from flwr.common.inflatable import check_body_len_consistency
24
25
  from flwr.common.logger import log
25
26
  from flwr.common.typing import InvalidRunStatusException
@@ -49,8 +50,9 @@ from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=
49
50
  from flwr.server.superlink.ffs.ffs_factory import FfsFactory
50
51
  from flwr.server.superlink.fleet.message_handler import message_handler
51
52
  from flwr.server.superlink.linkstate import LinkStateFactory
52
- from flwr.server.superlink.utils import abort_grpc_context
53
+ from flwr.server.superlink.utils import abort_grpc_context, check_abort
53
54
  from flwr.supercore.object_store import ObjectStoreFactory
55
+ from flwr.supercore.object_store.object_store import NoObjectInStoreError
54
56
 
55
57
 
56
58
  class FleetServicer(fleet_pb2_grpc.FleetServicer):
@@ -183,11 +185,39 @@ class FleetServicer(fleet_pb2_grpc.FleetServicer):
183
185
  request.object_id,
184
186
  )
185
187
 
188
+ state = self.state_factory.state()
189
+
190
+ # Abort if the run is not running
191
+ abort_msg = check_abort(
192
+ request.run_id,
193
+ [Status.PENDING, Status.STARTING, Status.FINISHED],
194
+ state,
195
+ )
196
+ if abort_msg:
197
+ abort_grpc_context(abort_msg, context)
198
+
199
+ if request.node.node_id not in state.get_nodes(run_id=request.run_id):
200
+ # Cancel insertion in ObjectStore
201
+ context.abort(grpc.StatusCode.FAILED_PRECONDITION, "Unexpected node ID.")
202
+
186
203
  if not check_body_len_consistency(request.object_content):
187
204
  # Cancel insertion in ObjectStore
188
- context.abort(grpc.StatusCode.PERMISSION_DENIED, "Unexpected object length")
205
+ context.abort(
206
+ grpc.StatusCode.FAILED_PRECONDITION, "Unexpected object length"
207
+ )
208
+
209
+ # Init store
210
+ store = self.objectstore_factory.store()
211
+
212
+ # Insert in store
213
+ stored = False
214
+ try:
215
+ store.put(request.object_id, request.object_content)
216
+ stored = True
217
+ except (NoObjectInStoreError, ValueError) as e:
218
+ log(ERROR, str(e))
189
219
 
190
- return PushObjectResponse()
220
+ return PushObjectResponse(stored=stored)
191
221
 
192
222
  def PullObject(
193
223
  self, request: PullObjectRequest, context: grpc.ServicerContext
@@ -199,4 +229,31 @@ class FleetServicer(fleet_pb2_grpc.FleetServicer):
199
229
  request.object_id,
200
230
  )
201
231
 
202
- return PullObjectResponse()
232
+ state = self.state_factory.state()
233
+
234
+ # Abort if the run is not running
235
+ abort_msg = check_abort(
236
+ request.run_id,
237
+ [Status.PENDING, Status.STARTING, Status.FINISHED],
238
+ state,
239
+ )
240
+ if abort_msg:
241
+ abort_grpc_context(abort_msg, context)
242
+
243
+ if request.node.node_id not in state.get_nodes(run_id=request.run_id):
244
+ # Cancel insertion in ObjectStore
245
+ context.abort(grpc.StatusCode.FAILED_PRECONDITION, "Unexpected node ID.")
246
+
247
+ # Init store
248
+ store = self.objectstore_factory.store()
249
+
250
+ # Fetch from store
251
+ content = store.get(request.object_id)
252
+ if content is not None:
253
+ object_available = content != b""
254
+ return PullObjectResponse(
255
+ object_found=True,
256
+ object_available=object_available,
257
+ object_content=content,
258
+ )
259
+ return PullObjectResponse(object_found=False, object_available=False)
@@ -409,11 +409,39 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
409
409
  """Push an object to the ObjectStore."""
410
410
  log(DEBUG, "ServerAppIoServicer.PushObject")
411
411
 
412
+ # Init state
413
+ state: LinkState = self.state_factory.state()
414
+
415
+ # Abort if the run is not running
416
+ abort_if(
417
+ request.run_id,
418
+ [Status.PENDING, Status.STARTING, Status.FINISHED],
419
+ state,
420
+ context,
421
+ )
422
+
423
+ if request.node.node_id != SUPERLINK_NODE_ID:
424
+ # Cancel insertion in ObjectStore
425
+ context.abort(grpc.StatusCode.FAILED_PRECONDITION, "Unexpected node ID.")
426
+
412
427
  if not check_body_len_consistency(request.object_content):
413
428
  # Cancel insertion in ObjectStore
414
- context.abort(grpc.StatusCode.PERMISSION_DENIED, "Unexpected object length")
429
+ context.abort(
430
+ grpc.StatusCode.FAILED_PRECONDITION, "Unexpected object length."
431
+ )
432
+
433
+ # Init store
434
+ store = self.objectstore_factory.store()
435
+
436
+ # Insert in store
437
+ stored = False
438
+ try:
439
+ store.put(request.object_id, request.object_content)
440
+ stored = True
441
+ except (NoObjectInStoreError, ValueError) as e:
442
+ log(ERROR, str(e))
415
443
 
416
- return PushObjectResponse()
444
+ return PushObjectResponse(stored=stored)
417
445
 
418
446
  def PullObject(
419
447
  self, request: PullObjectRequest, context: grpc.ServicerContext
@@ -421,7 +449,34 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
421
449
  """Pull an object from the ObjectStore."""
422
450
  log(DEBUG, "ServerAppIoServicer.PullObject")
423
451
 
424
- return PullObjectResponse()
452
+ # Init state
453
+ state: LinkState = self.state_factory.state()
454
+
455
+ # Abort if the run is not running
456
+ abort_if(
457
+ request.run_id,
458
+ [Status.PENDING, Status.STARTING, Status.FINISHED],
459
+ state,
460
+ context,
461
+ )
462
+
463
+ if request.node.node_id != SUPERLINK_NODE_ID:
464
+ # Cancel insertion in ObjectStore
465
+ context.abort(grpc.StatusCode.FAILED_PRECONDITION, "Unexpected node ID.")
466
+
467
+ # Init store
468
+ store = self.objectstore_factory.store()
469
+
470
+ # Fetch from store
471
+ content = store.get(request.object_id)
472
+ if content is not None:
473
+ object_available = content != b""
474
+ return PullObjectResponse(
475
+ object_found=True,
476
+ object_available=object_available,
477
+ object_content=content,
478
+ )
479
+ return PullObjectResponse(object_found=False, object_available=False)
425
480
 
426
481
 
427
482
  def _raise_if(validation_error: bool, request_name: str, detail: str) -> None:
@@ -42,8 +42,7 @@ from flwr.common.constant import (
42
42
  from flwr.common.exit import ExitCode, flwr_exit
43
43
  from flwr.common.exit_handlers import register_exit_handlers
44
44
  from flwr.common.logger import log
45
-
46
- from ..start_client_internal import start_client_internal
45
+ from flwr.supernode.start_client_internal import start_client_internal
47
46
 
48
47
 
49
48
  def flower_supernode() -> None:
@@ -48,6 +48,7 @@ def flwr_clientapp() -> None:
48
48
  token=args.token,
49
49
  flwr_dir=args.flwr_dir,
50
50
  certificates=None,
51
+ parent_pid=args.parent_pid,
51
52
  )
52
53
 
53
54
 
@@ -69,5 +70,12 @@ def _parse_args_run_flwr_clientapp() -> argparse.ArgumentParser:
69
70
  required=False,
70
71
  help="Unique token generated by SuperNode for each ClientApp execution",
71
72
  )
73
+ parser.add_argument(
74
+ "--parent-pid",
75
+ type=int,
76
+ default=None,
77
+ help="The PID of the parent process. When set, the process will terminate "
78
+ "when the parent process exits.",
79
+ )
72
80
  add_args_flwr_app_common(parser=parser)
73
81
  return parser
@@ -16,6 +16,8 @@
16
16
 
17
17
 
18
18
  import gc
19
+ import os
20
+ import threading
19
21
  import time
20
22
  from logging import DEBUG, ERROR, INFO
21
23
  from typing import Optional
@@ -54,14 +56,19 @@ from flwr.proto.clientappio_pb2 import (
54
56
  from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
55
57
 
56
58
 
57
- def run_clientapp( # pylint: disable=R0914
59
+ def run_clientapp( # pylint: disable=R0913, R0914, R0917
58
60
  clientappio_api_address: str,
59
61
  run_once: bool,
60
62
  token: Optional[int] = None,
61
63
  flwr_dir: Optional[str] = None,
62
64
  certificates: Optional[bytes] = None,
65
+ parent_pid: Optional[int] = None,
63
66
  ) -> None:
64
67
  """Run Flower ClientApp process."""
68
+ # Monitor the main process in case of SIGKILL
69
+ if parent_pid is not None:
70
+ start_parent_process_monitor(parent_pid)
71
+
65
72
  channel = create_channel(
66
73
  server_address=clientappio_api_address,
67
74
  insecure=(certificates is None),
@@ -151,6 +158,20 @@ def run_clientapp( # pylint: disable=R0914
151
158
  channel.close()
152
159
 
153
160
 
161
+ def start_parent_process_monitor(
162
+ parent_pid: int,
163
+ ) -> None:
164
+ """Monitor the parent process and exit if it terminates."""
165
+
166
+ def monitor() -> None:
167
+ while True:
168
+ time.sleep(0.2)
169
+ if os.getppid() != parent_pid:
170
+ os.kill(os.getpid(), 9)
171
+
172
+ threading.Thread(target=monitor, daemon=True).start()
173
+
174
+
154
175
  def get_token(stub: grpc.Channel) -> Optional[int]:
155
176
  """Get a token from SuperNode."""
156
177
  log(DEBUG, "[flwr-clientapp] Request token")
@@ -15,29 +15,25 @@
15
15
  """Main loop for Flower SuperNode."""
16
16
 
17
17
 
18
- import multiprocessing
19
18
  import os
20
- import sys
21
- import threading
19
+ import subprocess
22
20
  import time
23
21
  from collections.abc import Iterator
24
22
  from contextlib import contextmanager
25
23
  from logging import INFO, WARN
26
24
  from os import urandom
27
25
  from pathlib import Path
28
- from typing import Callable, Optional, Union
26
+ from typing import Callable, Optional, Union, cast
29
27
 
30
28
  import grpc
31
29
  from cryptography.hazmat.primitives.asymmetric import ec
32
30
  from grpc import RpcError
33
31
 
34
- from flwr.app.error import Error
35
- from flwr.cli.config_utils import get_fab_metadata
36
32
  from flwr.client.grpc_adapter_client.connection import grpc_adapter
37
33
  from flwr.client.grpc_rere_client.connection import grpc_request_response
38
- from flwr.client.run_info_store import DeprecatedRunInfoStore
39
- from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Message
34
+ from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Message, RecordDict
40
35
  from flwr.common.address import parse_address
36
+ from flwr.common.config import get_flwr_dir, get_fused_config_from_fab
41
37
  from flwr.common.constant import (
42
38
  CLIENT_OCTET,
43
39
  CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
@@ -49,7 +45,6 @@ from flwr.common.constant import (
49
45
  TRANSPORT_TYPE_GRPC_RERE,
50
46
  TRANSPORT_TYPE_REST,
51
47
  TRANSPORT_TYPES,
52
- ErrorCode,
53
48
  )
54
49
  from flwr.common.exit import ExitCode, flwr_exit
55
50
  from flwr.common.grpc import generic_create_grpc_server
@@ -57,10 +52,13 @@ from flwr.common.logger import log
57
52
  from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
58
53
  from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
59
54
  from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
60
- from flwr.supernode.cli.flwr_clientapp import flwr_clientapp
55
+ from flwr.server.superlink.ffs import FfsFactory
56
+ from flwr.supercore.object_store import ObjectStoreFactory
61
57
  from flwr.supernode.nodestate import NodeStateFactory
62
58
  from flwr.supernode.servicer.clientappio import ClientAppInputs, ClientAppIoServicer
63
59
 
60
+ DEFAULT_FFS_DIR = get_flwr_dir() / "supernode" / "ffs"
61
+
64
62
 
65
63
  # pylint: disable=import-outside-toplevel
66
64
  # pylint: disable=too-many-branches
@@ -139,13 +137,15 @@ def start_client_internal(
139
137
  certificates=None,
140
138
  )
141
139
 
142
- # DeprecatedRunInfoStore gets initialized when the first connection is established
143
- run_info_store: Optional[DeprecatedRunInfoStore] = None
140
+ # Initialize factories
144
141
  state_factory = NodeStateFactory()
145
- state = state_factory.state()
146
- mp_spawn_context = multiprocessing.get_context("spawn")
142
+ ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
143
+ object_store_factory = ObjectStoreFactory()
147
144
 
148
- runs: dict[int, Run] = {}
145
+ # Initialize NodeState, Ffs, and ObjectStore
146
+ state = state_factory.state()
147
+ ffs = ffs_factory.ffs()
148
+ _store = object_store_factory.store()
149
149
 
150
150
  with _init_connection(
151
151
  transport=transport,
@@ -158,73 +158,79 @@ def start_client_internal(
158
158
  ) as conn:
159
159
  receive, send, create_node, _, get_run, get_fab = conn
160
160
 
161
- # Register node when connecting the first time
162
- if run_info_store is None:
163
- # Call create_node fn to register node
164
- # and store node_id in state
165
- if (node_id := create_node()) is None:
166
- raise ValueError("Failed to register SuperNode with the SuperLink")
167
- state.set_node_id(node_id)
168
- run_info_store = DeprecatedRunInfoStore(
169
- node_id=state.get_node_id(),
170
- node_config=node_config,
171
- )
161
+ # Call create_node fn to register node
162
+ # and store node_id in state
163
+ if (node_id := create_node()) is None:
164
+ raise ValueError("Failed to register SuperNode with the SuperLink")
165
+ state.set_node_id(node_id)
172
166
 
173
167
  # pylint: disable=too-many-nested-blocks
174
168
  while True:
175
- try:
176
- # Receive
177
- message = receive()
178
- if message is None:
179
- time.sleep(3) # Wait for 3s before asking again
180
- continue
181
-
182
- log(INFO, "")
183
- if len(message.metadata.group_id) > 0:
184
- log(
185
- INFO,
186
- "[RUN %s, ROUND %s]",
187
- message.metadata.run_id,
188
- message.metadata.group_id,
189
- )
169
+ # Pull message
170
+ if (message := receive()) is None:
171
+ time.sleep(3)
172
+ continue
173
+
174
+ # Log message reception
175
+ log(INFO, "")
176
+ if message.metadata.group_id:
190
177
  log(
191
178
  INFO,
192
- "Received: %s message %s",
193
- message.metadata.message_type,
194
- message.metadata.message_id,
179
+ "[RUN %s, ROUND %s]",
180
+ message.metadata.run_id,
181
+ message.metadata.group_id,
195
182
  )
183
+ else:
184
+ log(INFO, "[RUN %s]", message.metadata.run_id)
185
+ log(
186
+ INFO,
187
+ "Received: %s message %s",
188
+ message.metadata.message_type,
189
+ message.metadata.message_id,
190
+ )
196
191
 
197
- # Get run info
198
- run_id = message.metadata.run_id
199
- if run_id not in runs:
200
- runs[run_id] = get_run(run_id)
201
-
202
- run: Run = runs[run_id]
203
- if get_fab is not None and run.fab_hash:
204
- fab = get_fab(run.fab_hash, run_id)
205
- fab_id, fab_version = get_fab_metadata(fab.content)
206
- else:
207
- fab = None
208
- fab_id, fab_version = run.fab_id, run.fab_version
209
-
210
- run.fab_id, run.fab_version = fab_id, fab_version
192
+ # Ensure the run and FAB are available
193
+ run_id = message.metadata.run_id
194
+ try:
195
+ # Check if the message is from an unknown run
196
+ if (run_info := state.get_run(run_id)) is None:
197
+ # Pull run info from SuperLink
198
+ run_info = get_run(run_id)
199
+ state.store_run(run_info)
200
+
201
+ # Pull and store the FAB
202
+ fab = get_fab(run_info.fab_hash, run_id)
203
+ ffs.put(fab.content, {})
204
+
205
+ # Initialize the context
206
+ run_cfg = get_fused_config_from_fab(fab.content, run_info)
207
+ run_ctx = Context(
208
+ run_id=run_id,
209
+ node_id=state.get_node_id(),
210
+ node_config=node_config,
211
+ state=RecordDict(),
212
+ run_config=run_cfg,
213
+ )
214
+ state.store_context(run_ctx)
211
215
 
212
- # Register context for this run
213
- run_info_store.register_context(
214
- run_id=run_id,
215
- run=run,
216
- flwr_path=flwr_path,
217
- fab=fab,
216
+ # Store the message in the state
217
+ state.store_message(message)
218
+ except RunNotRunningException:
219
+ log(
220
+ INFO,
221
+ "Run ID %s is not in `RUNNING` status. Ignoring message %s.",
222
+ run_id,
223
+ message.metadata.message_id,
218
224
  )
225
+ time.sleep(3)
226
+ continue
219
227
 
220
- # Retrieve context for this run
221
- context = run_info_store.retrieve_context(run_id=run_id)
222
- # Create an error reply message that will never be used to prevent
223
- # the used-before-assignment linting error
224
- reply_message = Message(
225
- Error(code=ErrorCode.UNKNOWN, reason="Unknown"),
226
- reply_to=message,
227
- )
228
+ try:
229
+ # Retrieve message, context, run and fab for this run
230
+ message = state.get_messages(run_ids=[run_id], is_reply=False)[0]
231
+ context = cast(Context, state.get_context(run_id))
232
+ run = cast(Run, state.get_run(run_id))
233
+ fab = Fab(run.fab_hash, ffs.get(run.fab_hash)[0]) # type: ignore
228
234
 
229
235
  # Two isolation modes:
230
236
  # 1. `subprocess`: SuperNode is starting the ClientApp
@@ -265,16 +271,11 @@ def start_client_internal(
265
271
  io_address,
266
272
  "--token",
267
273
  str(token),
274
+ "--parent-pid",
275
+ str(os.getpid()),
276
+ "--insecure",
268
277
  ]
269
- command.append("--insecure")
270
-
271
- proc = mp_spawn_context.Process(
272
- target=_run_flwr_clientapp,
273
- args=(command, os.getpid()),
274
- daemon=True,
275
- )
276
- proc.start()
277
- proc.join()
278
+ subprocess.run(command, check=False)
278
279
  else:
279
280
  # Wait for output to become available
280
281
  while not clientappio_servicer.has_outputs():
@@ -284,10 +285,7 @@ def start_client_internal(
284
285
  reply_message, context = outputs.message, outputs.context
285
286
 
286
287
  # Update node state
287
- run_info_store.update_context(
288
- run_id=run_id,
289
- context=context,
290
- )
288
+ state.store_context(context)
291
289
 
292
290
  # Send
293
291
  send(reply_message)
@@ -421,21 +419,6 @@ def _make_fleet_connection_retry_invoker(
421
419
  )
422
420
 
423
421
 
424
- def _run_flwr_clientapp(args: list[str], main_pid: int) -> None:
425
- # Monitor the main process in case of SIGKILL
426
- def main_process_monitor() -> None:
427
- while True:
428
- time.sleep(1)
429
- if os.getppid() != main_pid:
430
- os.kill(os.getpid(), 9)
431
-
432
- threading.Thread(target=main_process_monitor, daemon=True).start()
433
-
434
- # Run the command
435
- sys.argv = args
436
- flwr_clientapp()
437
-
438
-
439
422
  def run_clientappio_api_grpc(
440
423
  address: str,
441
424
  certificates: Optional[tuple[bytes, bytes, bytes]],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.19.0.dev20250602
3
+ Version: 1.19.0.dev20250604
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  License: Apache-2.0
6
6
  Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
@@ -84,7 +84,7 @@ flwr/client/grpc_adapter_client/__init__.py,sha256=RQWP5mFPROLHKgombiRvPXVWSoVrQ
84
84
  flwr/client/grpc_adapter_client/connection.py,sha256=aj5tTYyE8z2hQLXPPydsJiz8gBDIWLUhfWvqYkAL1L4,3966
85
85
  flwr/client/grpc_rere_client/__init__.py,sha256=i7iS0Lt8B7q0E2L72e4F_YrKm6ClRKnd71PNA6PW2O0,752
86
86
  flwr/client/grpc_rere_client/client_interceptor.py,sha256=zFaVHw6AxeNO-7eCKKb-RxrPa7zbM5Z-2-1Efc4adQY,2451
87
- flwr/client/grpc_rere_client/connection.py,sha256=slRDCax2CTwVURURUf8qV9Ph4eDuf67XVLB77PuV9fE,12118
87
+ flwr/client/grpc_rere_client/connection.py,sha256=kjnbUNLNz3fn-79fOQ-EDPIkzK8W0GckYIUkvWCXDD0,13283
88
88
  flwr/client/grpc_rere_client/grpc_adapter.py,sha256=JvMZ7vCFTaTEo6AzKYh3zDmeQAU7VSjdysbC6t3ufWg,6351
89
89
  flwr/client/message_handler/__init__.py,sha256=0lyljDVqre3WljiZbPcwCCf8GiIaSVI_yo_ylEyPwSE,719
90
90
  flwr/client/message_handler/message_handler.py,sha256=X9SXX6et97Lw9_DGD93HKsEBGNjXClcFgc_5aLK0oiU,6541
@@ -122,8 +122,8 @@ flwr/common/exit/exit_code.py,sha256=PNEnCrZfOILjfDAFu5m-2YWEJBrk97xglq4zCUlqV7E
122
122
  flwr/common/exit_handlers.py,sha256=IaqJ60fXZuu7McaRYnoYKtlbH9t4Yl9goNExKqtmQbs,4304
123
123
  flwr/common/grpc.py,sha256=manTaHaPiyYngUq1ErZvvV2B2GxlXUUUGRy3jc3TBIQ,9798
124
124
  flwr/common/heartbeat.py,sha256=SyEpNDnmJ0lni0cWO67rcoJVKasCLmkNHm3dKLeNrLU,5749
125
- flwr/common/inflatable.py,sha256=vBDlaJlgF6sryjglhFcr22zylROmPFwM7QLjVV7XbtU,6837
126
- flwr/common/inflatable_grpc_utils.py,sha256=e8uoQyuuhPlJiW359AuWrqcyRUtVRCP-v8M2hH-_U6U,4069
125
+ flwr/common/inflatable.py,sha256=9yPsSFOfNM2OIb15JQ6-wY5kblwXiC5zNX-tsp2ZwW0,7017
126
+ flwr/common/inflatable_grpc_utils.py,sha256=YGP8oJRfnkwvY6segWH1DUf_ljDIkku7-2zH66tv3HA,4337
127
127
  flwr/common/logger.py,sha256=JbRf6E2vQxXzpDBq1T8IDUJo_usu3gjWEBPQ6uKcmdg,13049
128
128
  flwr/common/message.py,sha256=HfSeqxwXgf90ilbMlM0vrF4cJWqJVx3jJ0gNmTfgdFw,19628
129
129
  flwr/common/object_ref.py,sha256=p3SfTeqo3Aj16SkB-vsnNn01zswOPdGNBitcbRnqmUk,9134
@@ -134,11 +134,11 @@ flwr/common/record/array.py,sha256=3K01tAf_jedub2r2-vkRshbsjBSiKErAO4KqDgdDaSo,1
134
134
  flwr/common/record/arrayrecord.py,sha256=CpoqYXM6Iv4XEc9SryCMYmw-bIvP8ut6xWJzRwYJzdU,18008
135
135
  flwr/common/record/configrecord.py,sha256=G7U0q39kB0Kyi0zMxFmPxcVemL9NgwVS1qjvI4BRQuU,9763
136
136
  flwr/common/record/conversion_utils.py,sha256=wbNCzy7oAqaA3-arhls_EqRZYXRC4YrWIoE-Gy82fJ0,1191
137
- flwr/common/record/metricrecord.py,sha256=XBPnIclQBRV_vHuvMk2sEdPjeyX5_Y00nuOHy8rASW8,8966
137
+ flwr/common/record/metricrecord.py,sha256=KOyJjJbvFV6IwBPbgm92FZ_0_hXpMHuwfCi1rh5Zddk,8954
138
138
  flwr/common/record/recorddict.py,sha256=p7hBimFpKM1XKUe6OAkR_7pYGzGL_EwUJUvJ8odZEcY,14986
139
139
  flwr/common/record/typeddict.py,sha256=dDKgUThs2BscYUNcgP82KP8-qfAYXYftDrf2LszAC_o,3599
140
140
  flwr/common/recorddict_compat.py,sha256=D5SqXWkqBddn5b6K_5UoH7aZ11UaN3lDTlzvHx3-rqk,14119
141
- flwr/common/retry_invoker.py,sha256=T6puUH3nCxdRzQHeanyr-0nTxhRiS1TH07rmef9vuLQ,14482
141
+ flwr/common/retry_invoker.py,sha256=s5IGgRovE19laMetHFePoqIdMBYfz_KdXs-KyfaCrXw,14634
142
142
  flwr/common/secure_aggregation/__init__.py,sha256=MgW6uHGhyFLBAYQqa1Vzs5n2Gc0d4yEw1_NmerFir70,731
143
143
  flwr/common/secure_aggregation/crypto/__init__.py,sha256=5E4q4-Fw0CNz4tLah_QHj7m_rDeM4ucHcFlPWB_Na3Q,738
144
144
  flwr/common/secure_aggregation/crypto/shamir.py,sha256=N8pPa5cEksowNoAqfFm5SP3IuxuVi9GGMa3JOtPniQY,3954
@@ -194,8 +194,8 @@ flwr/proto/log_pb2.py,sha256=iKaS3MVn1BS4xHu8uGPFCOi1KWtvVx-H9V4jCUIJghs,1393
194
194
  flwr/proto/log_pb2.pyi,sha256=ipuhgo40sAHTcRzCsGI1HwIstr5q0THPNk_cf62YyME,1448
195
195
  flwr/proto/log_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
196
196
  flwr/proto/log_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
197
- flwr/proto/message_pb2.py,sha256=0gwIorik6s5o7UF603mtRecDlQclPFwiDSlvkMxqzc0,4388
198
- flwr/proto/message_pb2.pyi,sha256=J1Y7Ok546KJXyvpShElhWLWNusfqww-K_7D6u0fFZbA,9072
197
+ flwr/proto/message_pb2.py,sha256=VoFv02FalR-xegoyqVMC1M_rD02sdWdoAAfFkw00k84,4481
198
+ flwr/proto/message_pb2.pyi,sha256=FmBgs2PsotAdv-OOCEHp4Dc3e6dSo6-CAB3-pgmva4g,9392
199
199
  flwr/proto/message_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
200
200
  flwr/proto/message_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
201
201
  flwr/proto/node_pb2.py,sha256=BzZfAWIX7lV62bZr9f7x16lUZcpg-EImxnwxQXgCbYg,1045
@@ -236,7 +236,7 @@ flwr/server/criterion.py,sha256=G4e-6B48Pc7d5rmGVUpIzNKb6UF88O3VmTRuUltgjzM,1061
236
236
  flwr/server/fleet_event_log_interceptor.py,sha256=AkL7Y5d3xm2vRhL3ahmEVVoOvAP7PA7dRgB-je4v-Ys,3774
237
237
  flwr/server/grid/__init__.py,sha256=aWZHezoR2UGMJISB_gPMCm2N_2GSbm97A3lAp7ruhRQ,888
238
238
  flwr/server/grid/grid.py,sha256=naGCYt5J6dnmUvrcGkdNyKPe3MBd-0awGm1ALmgahqY,6625
239
- flwr/server/grid/grpc_grid.py,sha256=PE_ZMYcqZOntFVurIc9aoD-DwUofVIyNTY2vKsx1U-M,11204
239
+ flwr/server/grid/grpc_grid.py,sha256=MWESNIUbBp8ownNE1JvWW-xQ7Hb7AyxcorVsOZIkI18,12321
240
240
  flwr/server/grid/inmemory_grid.py,sha256=RjejYT-d-hHuTs1KSs_5wvOdAWKLus8w5_UAcnGt4iw,6168
241
241
  flwr/server/history.py,sha256=cCkFhBN4GoHsYYNk5GG1Y089eKJh2DH_ZJbYPwLaGyk,5026
242
242
  flwr/server/run_serverapp.py,sha256=v0p6jXj2dFxlRUdoEeF1mnaFd9XRQi6dZCflPY6d3qI,2063
@@ -271,7 +271,7 @@ flwr/server/strategy/krum.py,sha256=9hjB-5l7lwo7Er2xRauYvNEKAv9KoPCin_TCdYJwQe4,
271
271
  flwr/server/strategy/qfedavg.py,sha256=-siSzfuVX8GRkjmyvbj68fnjk02E3EYHl8Ory6v1QzI,10131
272
272
  flwr/server/strategy/strategy.py,sha256=n4r52i5gK4KGToZvcJUeWuEif1tuI0HZUT3YJPTC7UE,7524
273
273
  flwr/server/superlink/__init__.py,sha256=GNSuJ4-N6Z8wun2iZNlXqENt5beUyzC0Gi_tN396bbM,707
274
- flwr/server/superlink/ffs/__init__.py,sha256=9CSIhFcskEtDM_F8hXpxgqKWz882xODEv4D5iZllac4,840
274
+ flwr/server/superlink/ffs/__init__.py,sha256=U3KXwG_SplEvchat27K0LYPoPHzh-cwwT_NHsGlYMt8,908
275
275
  flwr/server/superlink/ffs/disk_ffs.py,sha256=tkJiUa9cIq6Po-9UYMtFpI-GEyY5FMg4RcDKenaky74,3297
276
276
  flwr/server/superlink/ffs/ffs.py,sha256=6w7wy71i7tbuJwqEgdeCa49JejXMEof3jujURN_R7Rg,2395
277
277
  flwr/server/superlink/ffs/ffs_factory.py,sha256=pK-g3LMelvWTV6N9Cd-j-_-FdcGbRFTKNsWaqmlBDSk,1490
@@ -284,7 +284,7 @@ flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py,sha256=KouR9PUcrPmMtoLooF4O
284
284
  flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py,sha256=iSf0mbBAlig7G6subQwBSVjcUCgSihONKdZ1RmQPTOk,4887
285
285
  flwr/server/superlink/fleet/grpc_bidi/grpc_server.py,sha256=OsS-6GgCIzMMZDVu5Y-OKjynHVUrpdc_5OrtuB-IbU0,5174
286
286
  flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=ahDJJ1e-lDxBpeBMgPk7YZt2wB38_QltcpOC0gLbpFs,758
287
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=Y92dcvbaN4EHb4Rlut5O2saJsoAN_Tejs4iFtzCS0P0,7189
287
+ flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=SsMtj1EeOgumffWtYTQt-ii3JPldszXvP91C3axznq8,9176
288
288
  flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=DrHubsaLgJCwCeeJPYogQTiP0xYqjxwnT9rh7OP7BoU,6984
289
289
  flwr/server/superlink/fleet/message_handler/__init__.py,sha256=fHsRV0KvJ8HtgSA4_YBsEzuhJLjO8p6xx4aCY2oE1p4,731
290
290
  flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=P43PapLZJKbZ0Oo0kP_KcO5zSMvO53SakQgPMiR5d1M,6500
@@ -303,7 +303,7 @@ flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=sHJPK1w0tP0m2WCXH2F9l
303
303
  flwr/server/superlink/linkstate/utils.py,sha256=IeLh7iGRCHU5MEWOl7iriaSE4L__8GWOa2OleXadK5M,15444
304
304
  flwr/server/superlink/serverappio/__init__.py,sha256=Fy4zJuoccZe5mZSEIpOmQvU6YeXFBa1M4eZuXXmJcn8,717
305
305
  flwr/server/superlink/serverappio/serverappio_grpc.py,sha256=6-FUUt0GiLcBPljj8bBrUNeAITUoDQOLzaMihKo52hg,2326
306
- flwr/server/superlink/serverappio/serverappio_servicer.py,sha256=JkCqxYNV2Oou6CLdXndRzPjk281cSE4EVDKcuVh7jHE,15556
306
+ flwr/server/superlink/serverappio/serverappio_servicer.py,sha256=qInBXn7xcnNUNIXj_BkjoWfZd96By55gbTsp4onwfDQ,17290
307
307
  flwr/server/superlink/simulation/__init__.py,sha256=Ry8DrNaZCMcQXvUc4FoCN2m3dvUQgWjasfp015o3Ec4,718
308
308
  flwr/server/superlink/simulation/simulationio_grpc.py,sha256=0l0F-UjYEk6W7HZmI28PbJQLFxSi_vBHRkdchgdaSMQ,2224
309
309
  flwr/server/superlink/simulation/simulationio_servicer.py,sha256=aJezU8RSJswcmWm7Eoy0BqsU13jrcfuFwX3ljm-cORM,7719
@@ -345,19 +345,19 @@ flwr/superexec/simulation.py,sha256=j6YwUvBN7EQ09ID7MYOCVZ70PGbuyBy8f9bXU0EszEM,
345
345
  flwr/superlink/__init__.py,sha256=GNSuJ4-N6Z8wun2iZNlXqENt5beUyzC0Gi_tN396bbM,707
346
346
  flwr/supernode/__init__.py,sha256=KgeCaVvXWrU3rptNR1y0oBp4YtXbAcrnCcJAiOoWkI4,707
347
347
  flwr/supernode/cli/__init__.py,sha256=JuEMr0-s9zv-PEWKuLB9tj1ocNfroSyNJ-oyv7ati9A,887
348
- flwr/supernode/cli/flower_supernode.py,sha256=pr16i1xWDzxxB5lcRTaSd4DVQvVOC3G0zwLliS9jSZ0,8766
349
- flwr/supernode/cli/flwr_clientapp.py,sha256=ORsNxviXOKGzZdcp5DEiHIuj4RycgB2OaPDaTTJJWz4,2555
348
+ flwr/supernode/cli/flower_supernode.py,sha256=ly2AQhbla2sufDaMsENaEALDEd0a4CS4D0eUrUOkHzY,8778
349
+ flwr/supernode/cli/flwr_clientapp.py,sha256=KfVUO20ZMnUDSGZTJ9I1KkMawFsRV6kdRUmGIRNbg_8,2812
350
350
  flwr/supernode/nodestate/__init__.py,sha256=CyLLObbmmVgfRO88UCM0VMait1dL57mUauUDfuSHsbU,976
351
351
  flwr/supernode/nodestate/in_memory_nodestate.py,sha256=4ZiLA45fMi2bJgmfDNLtiv-gVNru95Bi48xBy7xtatA,5212
352
352
  flwr/supernode/nodestate/nodestate.py,sha256=SgblnKtqzTHRiODwg4QUREw1-uYPQrLzoeTBlROHf_0,4571
353
353
  flwr/supernode/nodestate/nodestate_factory.py,sha256=UYTDCcwK_baHUmkzkJDxL0UEqvtTfOMlQRrROMCd0Xo,1430
354
354
  flwr/supernode/runtime/__init__.py,sha256=JQdqd2EMTn-ORMeTvewYYh52ls0YKP68jrps1qioxu4,718
355
- flwr/supernode/runtime/run_clientapp.py,sha256=sEmrN1F-tV2YAzw06Dk4RM696yyP4xqm2gFLkp53Y6k,7402
355
+ flwr/supernode/runtime/run_clientapp.py,sha256=cvWSby7u31u97QapWHxJM-Wer6F1k6mbbD-d1gxwxZA,7962
356
356
  flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca8gxdEo,717
357
357
  flwr/supernode/servicer/clientappio/__init__.py,sha256=vJyOjO2FXZ2URbnthmdsgs6948wbYfdq1L1V8Um-Lr8,895
358
358
  flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=LmzkxtNQBn5vVrHc0Bhq2WqaK6-LM2v4kfLBN0PiNNM,8522
359
- flwr/supernode/start_client_internal.py,sha256=n8xmASWkJB1SngirdvpohjO2RCrGeasKwiLrOMjN4X8,17366
360
- flwr_nightly-1.19.0.dev20250602.dist-info/METADATA,sha256=csDAUbHgOviQ9Y9bb0VKlKRsI6i_wNi_SfUORnqWDr0,15910
361
- flwr_nightly-1.19.0.dev20250602.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
362
- flwr_nightly-1.19.0.dev20250602.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
363
- flwr_nightly-1.19.0.dev20250602.dist-info/RECORD,,
359
+ flwr/supernode/start_client_internal.py,sha256=5CwTNV-XmIhwR1jv3G7aQAXGhf6OFWS6U-vmxY1iKGA,16984
360
+ flwr_nightly-1.19.0.dev20250604.dist-info/METADATA,sha256=xUqCj0YV0Yt1jAVWil6lHAaQkDUMpQqRIFL5tX2yUQo,15910
361
+ flwr_nightly-1.19.0.dev20250604.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
362
+ flwr_nightly-1.19.0.dev20250604.dist-info/entry_points.txt,sha256=jNpDXGBGgs21RqUxelF_jwGaxtqFwm-MQyfz-ZqSjrA,367
363
+ flwr_nightly-1.19.0.dev20250604.dist-info/RECORD,,