flwr-nightly 1.21.0.dev20250831__py3-none-any.whl → 1.21.0.dev20250902__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/common/exit/__init__.py +4 -0
- flwr/common/exit/exit.py +4 -0
- flwr/common/exit/exit_code.py +5 -0
- flwr/common/exit/exit_handler.py +62 -0
- flwr/common/{exit_handlers.py → exit/signal_handler.py} +20 -37
- flwr/common/grpc.py +0 -11
- flwr/common/inflatable_utils.py +1 -1
- flwr/compat/server/app.py +2 -2
- flwr/server/app.py +12 -3
- flwr/supercore/cli/flower_superexec.py +3 -0
- flwr/supercore/grpc_health/__init__.py +3 -0
- flwr/supercore/grpc_health/health_server.py +53 -0
- flwr/supercore/grpc_health/simple_health_servicer.py +2 -2
- flwr/supercore/superexec/run_superexec.py +15 -3
- flwr/supernode/cli/flower_supernode.py +3 -0
- flwr/supernode/start_client_internal.py +15 -4
- {flwr_nightly-1.21.0.dev20250831.dist-info → flwr_nightly-1.21.0.dev20250902.dist-info}/METADATA +1 -1
- {flwr_nightly-1.21.0.dev20250831.dist-info → flwr_nightly-1.21.0.dev20250902.dist-info}/RECORD +20 -18
- {flwr_nightly-1.21.0.dev20250831.dist-info → flwr_nightly-1.21.0.dev20250902.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.21.0.dev20250831.dist-info → flwr_nightly-1.21.0.dev20250902.dist-info}/entry_points.txt +0 -0
flwr/common/exit/__init__.py
CHANGED
@@ -17,8 +17,12 @@
|
|
17
17
|
|
18
18
|
from .exit import flwr_exit
|
19
19
|
from .exit_code import ExitCode
|
20
|
+
from .exit_handler import add_exit_handler
|
21
|
+
from .signal_handler import register_signal_handlers
|
20
22
|
|
21
23
|
__all__ = [
|
22
24
|
"ExitCode",
|
25
|
+
"add_exit_handler",
|
23
26
|
"flwr_exit",
|
27
|
+
"register_signal_handlers",
|
24
28
|
]
|
flwr/common/exit/exit.py
CHANGED
@@ -26,6 +26,7 @@ from flwr.common.version import package_version
|
|
26
26
|
|
27
27
|
from ..logger import log
|
28
28
|
from .exit_code import EXIT_CODE_HELP
|
29
|
+
from .exit_handler import trigger_exit_handlers
|
29
30
|
|
30
31
|
HELP_PAGE_URL = (
|
31
32
|
f"https://flower.ai/docs/framework/v{package_version}/en/ref-exit-codes/"
|
@@ -80,6 +81,9 @@ def flwr_exit(
|
|
80
81
|
# Log the exit message
|
81
82
|
log(log_level, exit_message)
|
82
83
|
|
84
|
+
# Trigger exit handlers
|
85
|
+
trigger_exit_handlers()
|
86
|
+
|
83
87
|
# Exit
|
84
88
|
sys.exit(sys_exit_code)
|
85
89
|
|
flwr/common/exit/exit_code.py
CHANGED
@@ -36,6 +36,7 @@ class ExitCode:
|
|
36
36
|
|
37
37
|
# ServerApp-specific exit codes (200-299)
|
38
38
|
SERVERAPP_STRATEGY_PRECONDITION_UNMET = 200
|
39
|
+
SERVERAPP_STRATEGY_AGGREGATION_ERROR = 202
|
39
40
|
|
40
41
|
# SuperNode-specific exit codes (300-399)
|
41
42
|
SUPERNODE_REST_ADDRESS_INVALID = 300
|
@@ -89,6 +90,10 @@ EXIT_CODE_HELP = {
|
|
89
90
|
"perform weighted average (e.g. in FedAvg) please ensure the returned "
|
90
91
|
"MetricRecord from ClientApps do include this key."
|
91
92
|
),
|
93
|
+
ExitCode.SERVERAPP_STRATEGY_AGGREGATION_ERROR: (
|
94
|
+
"The strategy encountered an error during aggregation. Please check the logs "
|
95
|
+
"for more details."
|
96
|
+
),
|
92
97
|
# SuperNode-specific exit codes (300-399)
|
93
98
|
ExitCode.SUPERNODE_REST_ADDRESS_INVALID: (
|
94
99
|
"When using the REST API, please provide `https://` or "
|
@@ -0,0 +1,62 @@
|
|
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
|
+
"""Common function to register exit handlers."""
|
16
|
+
|
17
|
+
|
18
|
+
import signal
|
19
|
+
import threading
|
20
|
+
from typing import Callable
|
21
|
+
|
22
|
+
from .exit_code import ExitCode
|
23
|
+
|
24
|
+
SIGNAL_TO_EXIT_CODE: dict[int, int] = {
|
25
|
+
signal.SIGINT: ExitCode.GRACEFUL_EXIT_SIGINT,
|
26
|
+
signal.SIGTERM: ExitCode.GRACEFUL_EXIT_SIGTERM,
|
27
|
+
}
|
28
|
+
registered_exit_handlers: list[Callable[[], None]] = []
|
29
|
+
_lock_handlers = threading.Lock()
|
30
|
+
|
31
|
+
# SIGQUIT is not available on Windows
|
32
|
+
if hasattr(signal, "SIGQUIT"):
|
33
|
+
SIGNAL_TO_EXIT_CODE[signal.SIGQUIT] = ExitCode.GRACEFUL_EXIT_SIGQUIT
|
34
|
+
|
35
|
+
|
36
|
+
def add_exit_handler(exit_handler: Callable[[], None]) -> None:
|
37
|
+
"""Add an exit handler to be called on graceful exit.
|
38
|
+
|
39
|
+
This function allows you to register additional exit handlers
|
40
|
+
that will be executed when `flwr_exit` is called.
|
41
|
+
|
42
|
+
Parameters
|
43
|
+
----------
|
44
|
+
exit_handler : Callable[[], None]
|
45
|
+
A callable that takes no arguments and performs cleanup or
|
46
|
+
other actions before the application exits.
|
47
|
+
|
48
|
+
Notes
|
49
|
+
-----
|
50
|
+
The registered exit handlers will be called in LIFO order, i.e.,
|
51
|
+
the last registered handler will be the first to be called.
|
52
|
+
"""
|
53
|
+
with _lock_handlers:
|
54
|
+
registered_exit_handlers.append(exit_handler)
|
55
|
+
|
56
|
+
|
57
|
+
def trigger_exit_handlers() -> None:
|
58
|
+
"""Trigger all registered exit handlers in LIFO order."""
|
59
|
+
with _lock_handlers:
|
60
|
+
for handler in reversed(registered_exit_handlers):
|
61
|
+
handler()
|
62
|
+
registered_exit_handlers.clear()
|
@@ -12,7 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
# ==============================================================================
|
15
|
-
"""Common function to register
|
15
|
+
"""Common function to register signal handlers."""
|
16
16
|
|
17
17
|
|
18
18
|
import signal
|
@@ -24,20 +24,21 @@ from grpc import Server
|
|
24
24
|
|
25
25
|
from flwr.common.telemetry import EventType
|
26
26
|
|
27
|
-
from .exit import
|
27
|
+
from .exit import flwr_exit
|
28
|
+
from .exit_code import ExitCode
|
29
|
+
from .exit_handler import add_exit_handler
|
28
30
|
|
29
31
|
SIGNAL_TO_EXIT_CODE: dict[int, int] = {
|
30
32
|
signal.SIGINT: ExitCode.GRACEFUL_EXIT_SIGINT,
|
31
33
|
signal.SIGTERM: ExitCode.GRACEFUL_EXIT_SIGTERM,
|
32
34
|
}
|
33
|
-
registered_exit_handlers: list[Callable[[], None]] = []
|
34
35
|
|
35
36
|
# SIGQUIT is not available on Windows
|
36
37
|
if hasattr(signal, "SIGQUIT"):
|
37
38
|
SIGNAL_TO_EXIT_CODE[signal.SIGQUIT] = ExitCode.GRACEFUL_EXIT_SIGQUIT
|
38
39
|
|
39
40
|
|
40
|
-
def
|
41
|
+
def register_signal_handlers(
|
41
42
|
event_type: EventType,
|
42
43
|
exit_message: Optional[str] = None,
|
43
44
|
grpc_servers: Optional[list[Server]] = None,
|
@@ -63,7 +64,21 @@ def register_exit_handlers(
|
|
63
64
|
Additional exit handlers can be added using `add_exit_handler`.
|
64
65
|
"""
|
65
66
|
default_handlers: dict[int, Callable[[int, FrameType], None]] = {}
|
66
|
-
|
67
|
+
|
68
|
+
def _wait_to_stop() -> None:
|
69
|
+
if grpc_servers is not None:
|
70
|
+
for grpc_server in grpc_servers:
|
71
|
+
grpc_server.stop(grace=1)
|
72
|
+
|
73
|
+
if bckg_threads is not None:
|
74
|
+
for bckg_thread in bckg_threads:
|
75
|
+
bckg_thread.join()
|
76
|
+
|
77
|
+
# Ensure that `_wait_to_stop` is the last handler called on exit
|
78
|
+
add_exit_handler(_wait_to_stop)
|
79
|
+
|
80
|
+
for handler in exit_handlers or []:
|
81
|
+
add_exit_handler(handler)
|
67
82
|
|
68
83
|
def graceful_exit_handler(signalnum: int, _frame: FrameType) -> None:
|
69
84
|
"""Exit handler to be registered with `signal.signal`.
|
@@ -74,17 +89,6 @@ def register_exit_handlers(
|
|
74
89
|
# Reset to default handler
|
75
90
|
signal.signal(signalnum, default_handlers[signalnum]) # type: ignore
|
76
91
|
|
77
|
-
for handler in registered_exit_handlers:
|
78
|
-
handler()
|
79
|
-
|
80
|
-
if grpc_servers is not None:
|
81
|
-
for grpc_server in grpc_servers:
|
82
|
-
grpc_server.stop(grace=1)
|
83
|
-
|
84
|
-
if bckg_threads is not None:
|
85
|
-
for bckg_thread in bckg_threads:
|
86
|
-
bckg_thread.join()
|
87
|
-
|
88
92
|
# Setup things for graceful exit
|
89
93
|
flwr_exit(
|
90
94
|
code=SIGNAL_TO_EXIT_CODE[signalnum],
|
@@ -96,24 +100,3 @@ def register_exit_handlers(
|
|
96
100
|
for sig in SIGNAL_TO_EXIT_CODE:
|
97
101
|
default_handler = signal.signal(sig, graceful_exit_handler) # type: ignore
|
98
102
|
default_handlers[sig] = default_handler # type: ignore
|
99
|
-
|
100
|
-
|
101
|
-
def add_exit_handler(exit_handler: Callable[[], None]) -> None:
|
102
|
-
"""Add an exit handler to be called on graceful exit.
|
103
|
-
|
104
|
-
This function allows you to register additional exit handlers
|
105
|
-
that will be executed when the application exits gracefully,
|
106
|
-
if `register_exit_handlers` was called.
|
107
|
-
|
108
|
-
Parameters
|
109
|
-
----------
|
110
|
-
exit_handler : Callable[[], None]
|
111
|
-
A callable that takes no arguments and performs cleanup or
|
112
|
-
other actions before the application exits.
|
113
|
-
|
114
|
-
Notes
|
115
|
-
-----
|
116
|
-
This method is not thread-safe, and it allows you to add the
|
117
|
-
same exit handler multiple times.
|
118
|
-
"""
|
119
|
-
registered_exit_handlers.append(exit_handler)
|
flwr/common/grpc.py
CHANGED
@@ -23,9 +23,6 @@ from logging import DEBUG, ERROR
|
|
23
23
|
from typing import Any, Callable, Optional
|
24
24
|
|
25
25
|
import grpc
|
26
|
-
from grpc_health.v1.health_pb2_grpc import add_HealthServicer_to_server
|
27
|
-
|
28
|
-
from flwr.supercore.grpc_health import SimpleHealthServicer
|
29
26
|
|
30
27
|
from .address import is_port_in_use
|
31
28
|
from .logger import log
|
@@ -109,7 +106,6 @@ def generic_create_grpc_server( # pylint: disable=too-many-arguments, R0914, R0
|
|
109
106
|
keepalive_time_ms: int = 210000,
|
110
107
|
certificates: Optional[tuple[bytes, bytes, bytes]] = None,
|
111
108
|
interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None,
|
112
|
-
health_servicer: Optional[Any] = None,
|
113
109
|
) -> grpc.Server:
|
114
110
|
"""Create a gRPC server with a single servicer.
|
115
111
|
|
@@ -157,10 +153,6 @@ def generic_create_grpc_server( # pylint: disable=too-many-arguments, R0914, R0
|
|
157
153
|
* server private key.
|
158
154
|
interceptors : Optional[Sequence[grpc.ServerInterceptor]] (default: None)
|
159
155
|
A list of gRPC interceptors.
|
160
|
-
health_servicer : Optional[Any] (default: None)
|
161
|
-
An optional health servicer to add to the server. If provided, it should be an
|
162
|
-
instance of a class that inherits the `HealthServicer` class.
|
163
|
-
If None is provided, `SimpleHealthServicer` will be used by default.
|
164
156
|
|
165
157
|
Returns
|
166
158
|
-------
|
@@ -211,9 +203,6 @@ def generic_create_grpc_server( # pylint: disable=too-many-arguments, R0914, R0
|
|
211
203
|
)
|
212
204
|
add_servicer_to_server_fn(servicer, server)
|
213
205
|
|
214
|
-
# Enable health service
|
215
|
-
add_HealthServicer_to_server(health_servicer or SimpleHealthServicer(), server)
|
216
|
-
|
217
206
|
if certificates is not None:
|
218
207
|
if not valid_certificates(certificates):
|
219
208
|
sys.exit(1)
|
flwr/common/inflatable_utils.py
CHANGED
flwr/compat/server/app.py
CHANGED
@@ -22,7 +22,7 @@ from typing import Optional
|
|
22
22
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, event
|
23
23
|
from flwr.common.address import parse_address
|
24
24
|
from flwr.common.constant import FLEET_API_GRPC_BIDI_DEFAULT_ADDRESS
|
25
|
-
from flwr.common.
|
25
|
+
from flwr.common.exit import register_signal_handlers
|
26
26
|
from flwr.common.logger import log, warn_deprecated_feature
|
27
27
|
from flwr.server.client_manager import ClientManager
|
28
28
|
from flwr.server.history import History
|
@@ -154,7 +154,7 @@ def start_server( # pylint: disable=too-many-arguments,too-many-locals
|
|
154
154
|
)
|
155
155
|
|
156
156
|
# Graceful shutdown
|
157
|
-
|
157
|
+
register_signal_handlers(
|
158
158
|
event_type=EventType.START_SERVER_LEAVE,
|
159
159
|
exit_message="Flower server terminated gracefully.",
|
160
160
|
grpc_servers=[grpc_server],
|
flwr/server/app.py
CHANGED
@@ -57,8 +57,7 @@ from flwr.common.constant import (
|
|
57
57
|
ExecPluginType,
|
58
58
|
)
|
59
59
|
from flwr.common.event_log_plugin import EventLogWriterPlugin
|
60
|
-
from flwr.common.exit import ExitCode, flwr_exit
|
61
|
-
from flwr.common.exit_handlers import register_exit_handlers
|
60
|
+
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
62
61
|
from flwr.common.grpc import generic_create_grpc_server
|
63
62
|
from flwr.common.logger import log
|
64
63
|
from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
|
@@ -70,6 +69,7 @@ from flwr.proto.fleet_pb2_grpc import ( # pylint: disable=E0611
|
|
70
69
|
from flwr.proto.grpcadapter_pb2_grpc import add_GrpcAdapterServicer_to_server
|
71
70
|
from flwr.server.fleet_event_log_interceptor import FleetEventLogInterceptor
|
72
71
|
from flwr.supercore.ffs import FfsFactory
|
72
|
+
from flwr.supercore.grpc_health import add_args_health, run_health_server_grpc_no_tls
|
73
73
|
from flwr.supercore.object_store import ObjectStoreFactory
|
74
74
|
from flwr.superlink.servicer.control import run_control_api_grpc
|
75
75
|
|
@@ -176,6 +176,9 @@ def run_superlink() -> None:
|
|
176
176
|
serverappio_address, _, _ = _format_address(args.serverappio_api_address)
|
177
177
|
control_address, _, _ = _format_address(args.control_api_address)
|
178
178
|
simulationio_address, _, _ = _format_address(args.simulationio_api_address)
|
179
|
+
health_server_address = None
|
180
|
+
if args.health_server_address is not None:
|
181
|
+
health_server_address, _, _ = _format_address(args.health_server_address)
|
179
182
|
|
180
183
|
# Obtain certificates
|
181
184
|
certificates = try_obtain_server_certificates(args)
|
@@ -352,8 +355,13 @@ def run_superlink() -> None:
|
|
352
355
|
# pylint: disable-next=consider-using-with
|
353
356
|
subprocess.Popen(command)
|
354
357
|
|
358
|
+
# Launch gRPC health server
|
359
|
+
if health_server_address is not None:
|
360
|
+
health_server = run_health_server_grpc_no_tls(health_server_address)
|
361
|
+
grpc_servers.append(health_server)
|
362
|
+
|
355
363
|
# Graceful shutdown
|
356
|
-
|
364
|
+
register_signal_handlers(
|
357
365
|
event_type=EventType.RUN_SUPERLINK_LEAVE,
|
358
366
|
exit_message="SuperLink terminated gracefully.",
|
359
367
|
grpc_servers=grpc_servers,
|
@@ -610,6 +618,7 @@ def _parse_args_run_superlink() -> argparse.ArgumentParser:
|
|
610
618
|
_add_args_fleet_api(parser=parser)
|
611
619
|
_add_args_control_api(parser=parser)
|
612
620
|
_add_args_simulationio_api(parser=parser)
|
621
|
+
add_args_health(parser=parser)
|
613
622
|
|
614
623
|
return parser
|
615
624
|
|
@@ -25,6 +25,7 @@ from flwr.common.logger import log
|
|
25
25
|
from flwr.proto.clientappio_pb2_grpc import ClientAppIoStub
|
26
26
|
from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
|
27
27
|
from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
|
28
|
+
from flwr.supercore.grpc_health import add_args_health
|
28
29
|
from flwr.supercore.superexec.plugin import (
|
29
30
|
ClientAppExecPlugin,
|
30
31
|
ExecPlugin,
|
@@ -57,6 +58,7 @@ def flower_superexec() -> None:
|
|
57
58
|
appio_api_address=args.appio_api_address,
|
58
59
|
flwr_dir=args.flwr_dir,
|
59
60
|
parent_pid=args.parent_pid,
|
61
|
+
health_server_address=args.health_server_address,
|
60
62
|
)
|
61
63
|
|
62
64
|
|
@@ -100,6 +102,7 @@ def _parse_args() -> argparse.ArgumentParser:
|
|
100
102
|
help="The PID of the parent process. When set, the process will terminate "
|
101
103
|
"when the parent process exits.",
|
102
104
|
)
|
105
|
+
add_args_health(parser)
|
103
106
|
return parser
|
104
107
|
|
105
108
|
|
@@ -15,8 +15,11 @@
|
|
15
15
|
"""GRPC health servicers."""
|
16
16
|
|
17
17
|
|
18
|
+
from .health_server import add_args_health, run_health_server_grpc_no_tls
|
18
19
|
from .simple_health_servicer import SimpleHealthServicer
|
19
20
|
|
20
21
|
__all__ = [
|
21
22
|
"SimpleHealthServicer",
|
23
|
+
"add_args_health",
|
24
|
+
"run_health_server_grpc_no_tls",
|
22
25
|
]
|
@@ -0,0 +1,53 @@
|
|
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
|
+
"""Health servers."""
|
16
|
+
|
17
|
+
|
18
|
+
import argparse
|
19
|
+
from logging import INFO
|
20
|
+
|
21
|
+
import grpc
|
22
|
+
from grpc_health.v1.health_pb2_grpc import add_HealthServicer_to_server
|
23
|
+
|
24
|
+
from flwr.common.grpc import generic_create_grpc_server
|
25
|
+
from flwr.common.logger import log
|
26
|
+
|
27
|
+
from .simple_health_servicer import SimpleHealthServicer
|
28
|
+
|
29
|
+
|
30
|
+
def run_health_server_grpc_no_tls(address: str) -> grpc.Server:
|
31
|
+
"""Run gRPC health server with no TLS."""
|
32
|
+
health_server = generic_create_grpc_server(
|
33
|
+
servicer_and_add_fn=(
|
34
|
+
SimpleHealthServicer(),
|
35
|
+
add_HealthServicer_to_server,
|
36
|
+
),
|
37
|
+
server_address=address,
|
38
|
+
certificates=None,
|
39
|
+
)
|
40
|
+
log(INFO, "Starting gRPC health server on %s", address)
|
41
|
+
health_server.start()
|
42
|
+
return health_server
|
43
|
+
|
44
|
+
|
45
|
+
def add_args_health(parser: argparse.ArgumentParser) -> None:
|
46
|
+
"""Add arguments for health server."""
|
47
|
+
parser.add_argument(
|
48
|
+
"--health-server-address",
|
49
|
+
type=str,
|
50
|
+
default=None,
|
51
|
+
help="Health service gRPC server address (IPv4, IPv6, or a domain name) "
|
52
|
+
"with no TLS. If not set, the health server will not be started.",
|
53
|
+
)
|
@@ -28,11 +28,11 @@ class SimpleHealthServicer(HealthServicer): # type: ignore
|
|
28
28
|
"""A simple gRPC health servicer that always returns SERVING."""
|
29
29
|
|
30
30
|
def Check(
|
31
|
-
self, request: HealthCheckRequest, context: grpc.
|
31
|
+
self, request: HealthCheckRequest, context: grpc.ServicerContext
|
32
32
|
) -> HealthCheckResponse:
|
33
33
|
"""Return a HealthCheckResponse with SERVING status."""
|
34
34
|
return HealthCheckResponse(status=HealthCheckResponse.SERVING)
|
35
35
|
|
36
|
-
def Watch(self, request: HealthCheckRequest, context: grpc.
|
36
|
+
def Watch(self, request: HealthCheckRequest, context: grpc.ServicerContext) -> None:
|
37
37
|
"""Watch the health status (not implemented)."""
|
38
38
|
context.abort(grpc.StatusCode.UNIMPLEMENTED, "Watch is not implemented")
|
@@ -20,7 +20,7 @@ from logging import WARN
|
|
20
20
|
from typing import Optional, Union
|
21
21
|
|
22
22
|
from flwr.common.config import get_flwr_dir
|
23
|
-
from flwr.common.
|
23
|
+
from flwr.common.exit import register_signal_handlers
|
24
24
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
25
25
|
from flwr.common.logger import log
|
26
26
|
from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
|
@@ -36,11 +36,12 @@ from flwr.proto.run_pb2 import GetRunRequest # pylint: disable=E0611
|
|
36
36
|
from flwr.proto.serverappio_pb2_grpc import ServerAppIoStub
|
37
37
|
from flwr.proto.simulationio_pb2_grpc import SimulationIoStub
|
38
38
|
from flwr.supercore.app_utils import start_parent_process_monitor
|
39
|
+
from flwr.supercore.grpc_health import run_health_server_grpc_no_tls
|
39
40
|
|
40
41
|
from .plugin import ExecPlugin
|
41
42
|
|
42
43
|
|
43
|
-
def run_superexec(
|
44
|
+
def run_superexec( # pylint: disable=R0913,R0914,R0917
|
44
45
|
plugin_class: type[ExecPlugin],
|
45
46
|
stub_class: Union[
|
46
47
|
type[ClientAppIoStub], type[ServerAppIoStub], type[SimulationIoStub]
|
@@ -48,6 +49,7 @@ def run_superexec(
|
|
48
49
|
appio_api_address: str,
|
49
50
|
flwr_dir: Optional[str] = None,
|
50
51
|
parent_pid: Optional[int] = None,
|
52
|
+
health_server_address: Optional[str] = None,
|
51
53
|
) -> None:
|
52
54
|
"""Run Flower SuperExec.
|
53
55
|
|
@@ -64,11 +66,20 @@ def run_superexec(
|
|
64
66
|
parent_pid : Optional[int] (default: None)
|
65
67
|
The PID of the parent process. If provided, the SuperExec will terminate
|
66
68
|
when the parent process exits.
|
69
|
+
health_server_address : Optional[str] (default: None)
|
70
|
+
The address of the health server. If `None` is provided, the health server will
|
71
|
+
NOT be started.
|
67
72
|
"""
|
68
73
|
# Start monitoring the parent process if a PID is provided
|
69
74
|
if parent_pid is not None:
|
70
75
|
start_parent_process_monitor(parent_pid)
|
71
76
|
|
77
|
+
# Launch gRPC health server
|
78
|
+
grpc_servers = []
|
79
|
+
if health_server_address is not None:
|
80
|
+
health_server = run_health_server_grpc_no_tls(health_server_address)
|
81
|
+
grpc_servers.append(health_server)
|
82
|
+
|
72
83
|
# Create the channel to the AppIO API
|
73
84
|
# No TLS support for now, so insecure connection
|
74
85
|
channel = create_channel(
|
@@ -79,9 +90,10 @@ def run_superexec(
|
|
79
90
|
channel.subscribe(on_channel_state_change)
|
80
91
|
|
81
92
|
# Register exit handlers to close the channel on exit
|
82
|
-
|
93
|
+
register_signal_handlers(
|
83
94
|
event_type=EventType.RUN_SUPEREXEC_LEAVE,
|
84
95
|
exit_message="SuperExec terminated gracefully.",
|
96
|
+
grpc_servers=grpc_servers,
|
85
97
|
exit_handlers=[lambda: channel.close()], # pylint: disable=W0108
|
86
98
|
)
|
87
99
|
|
@@ -41,6 +41,7 @@ from flwr.common.constant import (
|
|
41
41
|
)
|
42
42
|
from flwr.common.exit import ExitCode, flwr_exit
|
43
43
|
from flwr.common.logger import log
|
44
|
+
from flwr.supercore.grpc_health import add_args_health
|
44
45
|
from flwr.supernode.start_client_internal import start_client_internal
|
45
46
|
|
46
47
|
|
@@ -79,6 +80,7 @@ def flower_supernode() -> None:
|
|
79
80
|
flwr_path=args.flwr_dir,
|
80
81
|
isolation=args.isolation,
|
81
82
|
clientappio_api_address=args.clientappio_api_address,
|
83
|
+
health_server_address=args.health_server_address,
|
82
84
|
)
|
83
85
|
|
84
86
|
|
@@ -118,6 +120,7 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser:
|
|
118
120
|
help="ClientAppIo API (gRPC) server address (IPv4, IPv6, or a domain name). "
|
119
121
|
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS}.",
|
120
122
|
)
|
123
|
+
add_args_health(parser)
|
121
124
|
|
122
125
|
return parser
|
123
126
|
|
@@ -43,8 +43,7 @@ from flwr.common.constant import (
|
|
43
43
|
TRANSPORT_TYPES,
|
44
44
|
ExecPluginType,
|
45
45
|
)
|
46
|
-
from flwr.common.exit import ExitCode, flwr_exit
|
47
|
-
from flwr.common.exit_handlers import register_exit_handlers
|
46
|
+
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
48
47
|
from flwr.common.grpc import generic_create_grpc_server
|
49
48
|
from flwr.common.inflatable import iterate_object_tree
|
50
49
|
from flwr.common.inflatable_utils import (
|
@@ -58,6 +57,7 @@ from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
|
58
57
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
59
58
|
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
60
59
|
from flwr.supercore.ffs import Ffs, FfsFactory
|
60
|
+
from flwr.supercore.grpc_health import run_health_server_grpc_no_tls
|
61
61
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
62
62
|
from flwr.supernode.nodestate import NodeState, NodeStateFactory
|
63
63
|
from flwr.supernode.servicer.clientappio import ClientAppIoServicer
|
@@ -85,6 +85,7 @@ def start_client_internal(
|
|
85
85
|
flwr_path: Optional[Path] = None,
|
86
86
|
isolation: str = ISOLATION_MODE_SUBPROCESS,
|
87
87
|
clientappio_api_address: str = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
88
|
+
health_server_address: Optional[str] = None,
|
88
89
|
) -> None:
|
89
90
|
"""Start a Flower client node which connects to a Flower server.
|
90
91
|
|
@@ -133,6 +134,9 @@ def start_client_internal(
|
|
133
134
|
clientappio_api_address : str
|
134
135
|
(default: `CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS`)
|
135
136
|
The SuperNode gRPC server address.
|
137
|
+
health_server_address : Optional[str] (default: None)
|
138
|
+
The address of the health server. If `None` is provided, the health server will
|
139
|
+
NOT be started.
|
136
140
|
"""
|
137
141
|
if insecure is None:
|
138
142
|
insecure = root_certificates is None
|
@@ -143,6 +147,7 @@ def start_client_internal(
|
|
143
147
|
object_store_factory = ObjectStoreFactory()
|
144
148
|
|
145
149
|
# Launch ClientAppIo API server
|
150
|
+
grpc_servers = []
|
146
151
|
clientappio_server = run_clientappio_api_grpc(
|
147
152
|
address=clientappio_api_address,
|
148
153
|
state_factory=state_factory,
|
@@ -150,12 +155,18 @@ def start_client_internal(
|
|
150
155
|
objectstore_factory=object_store_factory,
|
151
156
|
certificates=None,
|
152
157
|
)
|
158
|
+
grpc_servers.append(clientappio_server)
|
159
|
+
|
160
|
+
# Launch gRPC health server
|
161
|
+
if health_server_address is not None:
|
162
|
+
health_server = run_health_server_grpc_no_tls(health_server_address)
|
163
|
+
grpc_servers.append(health_server)
|
153
164
|
|
154
165
|
# Register handlers for graceful shutdown
|
155
|
-
|
166
|
+
register_signal_handlers(
|
156
167
|
event_type=EventType.RUN_SUPERNODE_LEAVE,
|
157
168
|
exit_message="SuperNode terminated gracefully.",
|
158
|
-
grpc_servers=
|
169
|
+
grpc_servers=grpc_servers,
|
159
170
|
)
|
160
171
|
|
161
172
|
# Initialize NodeState, Ffs, and ObjectStore
|
{flwr_nightly-1.21.0.dev20250831.dist-info → flwr_nightly-1.21.0.dev20250902.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: flwr-nightly
|
3
|
-
Version: 1.21.0.
|
3
|
+
Version: 1.21.0.dev20250902
|
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
|
{flwr_nightly-1.21.0.dev20250831.dist-info → flwr_nightly-1.21.0.dev20250902.dist-info}/RECORD
RENAMED
@@ -121,15 +121,16 @@ flwr/common/differential_privacy_constants.py,sha256=ruEjH4qF_S2bgxRI6brWCGWQPxF
|
|
121
121
|
flwr/common/dp.py,sha256=ftqWheOICK5N_zPaofnbFb474lMb5w9lclwxf5DKY0w,1978
|
122
122
|
flwr/common/event_log_plugin/__init__.py,sha256=ts3VAL3Fk6Grp1EK_1Qg_V-BfOof9F86iBx4rbrEkyo,838
|
123
123
|
flwr/common/event_log_plugin/event_log_plugin.py,sha256=4SkVa1Ic-sPlICJShBuggXmXDcQtWQ1KDby4kthFNF0,2064
|
124
|
-
flwr/common/exit/__init__.py,sha256
|
125
|
-
flwr/common/exit/exit.py,sha256=
|
126
|
-
flwr/common/exit/exit_code.py,sha256=
|
127
|
-
flwr/common/
|
128
|
-
flwr/common/
|
124
|
+
flwr/common/exit/__init__.py,sha256=8W7xaO1iw0vacgmQW7FTFbSh7csNv6XfsgIlnIbNF6U,978
|
125
|
+
flwr/common/exit/exit.py,sha256=DcXJfbpW1g-pQJqSZmps-1MZydd7T7RaarghIf2e4tU,3636
|
126
|
+
flwr/common/exit/exit_code.py,sha256=m2B3mOKodKenDAG0bV2nWoj5ERlIkZ7UiShJQk1FnF0,5132
|
127
|
+
flwr/common/exit/exit_handler.py,sha256=uzDdWwhKgc1w5csZS52b86kjmEApmDZKwMn_X0zDZZo,2126
|
128
|
+
flwr/common/exit/signal_handler.py,sha256=wqxykrwgmpFzmEMhpnlM7RtO0PnqIvYiSB1qYahZ5Sk,3710
|
129
|
+
flwr/common/grpc.py,sha256=nHnFC7E84pZVTvd6BhcSYWnGd0jf8t5UmGea04qvilM,9806
|
129
130
|
flwr/common/heartbeat.py,sha256=SyEpNDnmJ0lni0cWO67rcoJVKasCLmkNHm3dKLeNrLU,5749
|
130
131
|
flwr/common/inflatable.py,sha256=GDL9oBKs16_yyVdlH6kBf493O5xll_h9V7XB5Mpx1Hc,9524
|
131
132
|
flwr/common/inflatable_protobuf_utils.py,sha256=JtRqp-fV47goDM2y8JRQ7AmwwjeGaWexwoMWLcxX5gE,5056
|
132
|
-
flwr/common/inflatable_utils.py,sha256=
|
133
|
+
flwr/common/inflatable_utils.py,sha256=njQB7u4OL4AMraRgx23L84JEz5upeXMuYWaPZJ_T-aM,18946
|
133
134
|
flwr/common/logger.py,sha256=kP7Cbs2WuYFK83Wsx5o9qc9mj8jDSyUK3BfRjvxhSTQ,13049
|
134
135
|
flwr/common/message.py,sha256=xAL7iZN5-n-xPQpgoSFvxNrzs8fmiiPfoU0DjNQEhRw,19953
|
135
136
|
flwr/common/object_ref.py,sha256=p3SfTeqo3Aj16SkB-vsnNn01zswOPdGNBitcbRnqmUk,9134
|
@@ -166,7 +167,7 @@ flwr/compat/client/grpc_client/__init__.py,sha256=MDOckOODn-FJnkkFEfb2JO-2G97wrB
|
|
166
167
|
flwr/compat/client/grpc_client/connection.py,sha256=xAyvcTVr7bkwUfR5P3D_LKlZYiyySpt5sEwORA1h8Gc,9189
|
167
168
|
flwr/compat/common/__init__.py,sha256=OMnKw4ad0qYMSIA9LZRa2gOkhSOXwAZCpAHnBQE_hFc,746
|
168
169
|
flwr/compat/server/__init__.py,sha256=TGVSoOTuf5T5JHUVrK5wuorQF7L6Wvdem8B4uufvMJY,746
|
169
|
-
flwr/compat/server/app.py,sha256=
|
170
|
+
flwr/compat/server/app.py,sha256=l6G2T2uwBjoXO3bRuI5mAzazRQNtJHUkpCwXOigoM8Y,6485
|
170
171
|
flwr/compat/simulation/__init__.py,sha256=MApGa-tysDDw34iSdxZ7TWOKtGJM-z3i8fIRJa0qbZ8,750
|
171
172
|
flwr/proto/__init__.py,sha256=S3VbQzVwNC1P-3_9EdrXuwgptO-BVuuAe20Z_OUc1cQ,683
|
172
173
|
flwr/proto/appio_pb2.py,sha256=IhwG71f9igeXO8QCuU6vwxETlFka0KR2PiMMNvmAYgs,4029
|
@@ -235,7 +236,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
|
|
235
236
|
flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
|
236
237
|
flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
237
238
|
flwr/server/__init__.py,sha256=LQQHiuL2jy7TpNaKastRdGsexlxSt5ZWAQNVqitDnrY,1598
|
238
|
-
flwr/server/app.py,sha256=
|
239
|
+
flwr/server/app.py,sha256=BYFMIpzD7ZbxW9xMhW3mJ2Ojw0E71vITS3QKf6Bse3I,29286
|
239
240
|
flwr/server/client_manager.py,sha256=5jCGavVli7XdupvWWo7ru3PdFTlRU8IGvHFSSoUVLRs,6227
|
240
241
|
flwr/server/client_proxy.py,sha256=sv0E9AldBYOvc3pusqFh-GnyreeMfsXQ1cuTtxTq_wY,2399
|
241
242
|
flwr/server/compat/__init__.py,sha256=0IsttWvY15qO98_1GyzVC-vR1e_ZPXOdu2qUlOkYMPE,886
|
@@ -344,15 +345,16 @@ flwr/simulation/simulationio_connection.py,sha256=mzS1C6EEREwQDPceDo30anAasmTDLb
|
|
344
345
|
flwr/supercore/__init__.py,sha256=pqkFoow_E6UhbBlhmoD1gmTH-33yJRhBsIZqxRPFZ7U,755
|
345
346
|
flwr/supercore/app_utils.py,sha256=K76Zt6R670b1hUmxOsNc1WUCVYvF7lejXPcCO9K0Q0g,1753
|
346
347
|
flwr/supercore/cli/__init__.py,sha256=EDl2aO-fuQfxSbL-T1W9RAfA2N0hpWHmqX_GSwblJbQ,845
|
347
|
-
flwr/supercore/cli/flower_superexec.py,sha256=
|
348
|
+
flwr/supercore/cli/flower_superexec.py,sha256=6fbzv71VYZ7nqrJQG8ZUKYVgL9LPIFFDw7uvDlkCd-o,4225
|
348
349
|
flwr/supercore/corestate/__init__.py,sha256=Vau6-L_JG5QzNqtCTa9xCKGGljc09wY8avZmIjSJemg,774
|
349
350
|
flwr/supercore/corestate/corestate.py,sha256=rDAWWeG5DcpCyQso9Z3RhwL4zr2IroPlRMcDzqoSu8s,2328
|
350
351
|
flwr/supercore/ffs/__init__.py,sha256=U3KXwG_SplEvchat27K0LYPoPHzh-cwwT_NHsGlYMt8,908
|
351
352
|
flwr/supercore/ffs/disk_ffs.py,sha256=c5VywSaRnq3XM_zuJptNtsF2HFwsRK0pvBd5-5CNONs,3272
|
352
353
|
flwr/supercore/ffs/ffs.py,sha256=6w7wy71i7tbuJwqEgdeCa49JejXMEof3jujURN_R7Rg,2395
|
353
354
|
flwr/supercore/ffs/ffs_factory.py,sha256=pK-g3LMelvWTV6N9Cd-j-_-FdcGbRFTKNsWaqmlBDSk,1490
|
354
|
-
flwr/supercore/grpc_health/__init__.py,sha256=
|
355
|
-
flwr/supercore/grpc_health/
|
355
|
+
flwr/supercore/grpc_health/__init__.py,sha256=rCPZ7ucPY9NXBWuhb9iKs2D-yZ0rQ5YkJ7LlOeahN9A,948
|
356
|
+
flwr/supercore/grpc_health/health_server.py,sha256=bwNzQ1eQJWmzeSJwqa0zrD5s_Cut00vKdSSk8NhPI2c,1808
|
357
|
+
flwr/supercore/grpc_health/simple_health_servicer.py,sha256=C5QN_cR_VBqbhva1eP8zyZ6YwS5Q9APKl6WgM03jAC0,1531
|
356
358
|
flwr/supercore/license_plugin/__init__.py,sha256=d8OgHTn2BwjoNSPy8jQQxTC_iT3-ENLwKM8yhHKvCRM,820
|
357
359
|
flwr/supercore/license_plugin/license_plugin.py,sha256=BFhlCH5v9KKuY7crVCsi8fuYe98SJfnGxRS0CVc_Y5I,948
|
358
360
|
flwr/supercore/object_store/__init__.py,sha256=cdfPAmjINY6iOp8oI_LdcVh2simg469Mkdl4LLV4kHI,911
|
@@ -367,7 +369,7 @@ flwr/supercore/superexec/plugin/clientapp_exec_plugin.py,sha256=9FT6ufEqV5K9g4Fa
|
|
367
369
|
flwr/supercore/superexec/plugin/exec_plugin.py,sha256=w3jmtxdv7ov_EdAgifKcm4q8nV39e2Xna4sNjqClwOM,2447
|
368
370
|
flwr/supercore/superexec/plugin/serverapp_exec_plugin.py,sha256=IwRzdPV-cSKwrP2krGh0De4IkAuxsmgK0WU6J-2GXqM,1035
|
369
371
|
flwr/supercore/superexec/plugin/simulation_exec_plugin.py,sha256=upn5zE-YKkl_jTw8RzmeyQ58PU_UAlQ7CqnBXXdng8I,1060
|
370
|
-
flwr/supercore/superexec/run_superexec.py,sha256=
|
372
|
+
flwr/supercore/superexec/run_superexec.py,sha256=8hUlaVPVNnhePQ9OUgen4yy0fSGZAVggBGzm-33iJPw,6630
|
371
373
|
flwr/supercore/utils.py,sha256=ebuHMbeA8eXisX0oMPqBK3hk7uVnIE_yiqWVz8YbkpQ,1324
|
372
374
|
flwr/superlink/__init__.py,sha256=GNSuJ4-N6Z8wun2iZNlXqENt5beUyzC0Gi_tN396bbM,707
|
373
375
|
flwr/superlink/servicer/__init__.py,sha256=ZC-kILcUGeh6IxJsfu24cTzUqIGXmQfEKsGfhsnhBpM,717
|
@@ -379,7 +381,7 @@ flwr/superlink/servicer/control/control_servicer.py,sha256=5GMGcq5_Hvac7DpVg8TWU
|
|
379
381
|
flwr/superlink/servicer/control/control_user_auth_interceptor.py,sha256=9Aqhrt_UX80FXbIQVXUrqDHs5rD5CA7vEn0Bh-zPiYU,6232
|
380
382
|
flwr/supernode/__init__.py,sha256=KgeCaVvXWrU3rptNR1y0oBp4YtXbAcrnCcJAiOoWkI4,707
|
381
383
|
flwr/supernode/cli/__init__.py,sha256=JuEMr0-s9zv-PEWKuLB9tj1ocNfroSyNJ-oyv7ati9A,887
|
382
|
-
flwr/supernode/cli/flower_supernode.py,sha256=
|
384
|
+
flwr/supernode/cli/flower_supernode.py,sha256=7aBm0z03OU-npVd1onLCvUotyhSvlZLxAnFkGVMhZcw,8670
|
383
385
|
flwr/supernode/cli/flwr_clientapp.py,sha256=W3tAyqSfeHbPhqBC4Pfo9bsyFdkBKPqdKlhGmeUKwKg,3173
|
384
386
|
flwr/supernode/nodestate/__init__.py,sha256=CyLLObbmmVgfRO88UCM0VMait1dL57mUauUDfuSHsbU,976
|
385
387
|
flwr/supernode/nodestate/in_memory_nodestate.py,sha256=rr_tg7YXhf_seYFipSB59TAfheKPratx3rrvHUOJ80g,7343
|
@@ -390,8 +392,8 @@ flwr/supernode/runtime/run_clientapp.py,sha256=2vknOFxmexCv0vsIsymrtRcvvgmk1tHM5
|
|
390
392
|
flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca8gxdEo,717
|
391
393
|
flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
|
392
394
|
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=nIHRu38EWK-rpNOkcgBRAAKwYQQWFeCwu0lkO7OPZGQ,10239
|
393
|
-
flwr/supernode/start_client_internal.py,sha256=
|
394
|
-
flwr_nightly-1.21.0.
|
395
|
-
flwr_nightly-1.21.0.
|
396
|
-
flwr_nightly-1.21.0.
|
397
|
-
flwr_nightly-1.21.0.
|
395
|
+
flwr/supernode/start_client_internal.py,sha256=Y9S1-QlO2WP6eo4JvWzIpfaCoh2aoE7bjEYyxNNnlyg,20777
|
396
|
+
flwr_nightly-1.21.0.dev20250902.dist-info/METADATA,sha256=9h7kKXkJ1vRQxfB4fvu38Qh4FK-01OrgTn1sRzeqxdo,15967
|
397
|
+
flwr_nightly-1.21.0.dev20250902.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
398
|
+
flwr_nightly-1.21.0.dev20250902.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
|
399
|
+
flwr_nightly-1.21.0.dev20250902.dist-info/RECORD,,
|
{flwr_nightly-1.21.0.dev20250831.dist-info → flwr_nightly-1.21.0.dev20250902.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|