flwr-nightly 1.13.0.dev20241114__py3-none-any.whl → 1.13.0.dev20241116__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.
Potentially problematic release.
This version of flwr-nightly might be problematic. Click here for more details.
- flwr/client/app.py +39 -11
- flwr/client/clientapp/app.py +29 -22
- flwr/client/supernode/app.py +29 -17
- flwr/common/constant.py +21 -6
- flwr/server/app.py +37 -12
- flwr/server/driver/grpc_driver.py +2 -2
- flwr/server/run_serverapp.py +8 -238
- flwr/server/serverapp/app.py +33 -22
- flwr/server/superlink/linkstate/in_memory_linkstate.py +55 -28
- flwr/server/superlink/linkstate/linkstate.py +19 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +70 -111
- flwr/server/superlink/linkstate/utils.py +182 -3
- flwr/simulation/app.py +3 -3
- flwr/simulation/legacy_app.py +21 -1
- flwr/simulation/simulationio_connection.py +2 -2
- flwr/superexec/deployment.py +9 -5
- {flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/METADATA +5 -4
- {flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/RECORD +21 -21
- {flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.13.0.dev20241114.dist-info → flwr_nightly-1.13.0.dev20241116.dist-info}/entry_points.txt +0 -0
flwr/server/run_serverapp.py
CHANGED
|
@@ -15,33 +15,15 @@
|
|
|
15
15
|
"""Run ServerApp."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import argparse
|
|
19
18
|
import sys
|
|
20
|
-
from logging import DEBUG,
|
|
21
|
-
from pathlib import Path
|
|
19
|
+
from logging import DEBUG, ERROR
|
|
22
20
|
from typing import Optional
|
|
23
21
|
|
|
24
|
-
from flwr.
|
|
25
|
-
from flwr.
|
|
26
|
-
from flwr.common import Context, EventType, RecordSet, event
|
|
27
|
-
from flwr.common.config import (
|
|
28
|
-
get_flwr_dir,
|
|
29
|
-
get_fused_config_from_dir,
|
|
30
|
-
get_metadata_from_config,
|
|
31
|
-
get_project_config,
|
|
32
|
-
get_project_dir,
|
|
33
|
-
)
|
|
34
|
-
from flwr.common.constant import SERVERAPPIO_API_DEFAULT_ADDRESS
|
|
35
|
-
from flwr.common.logger import log, update_console_handler, warn_deprecated_feature
|
|
22
|
+
from flwr.common import Context
|
|
23
|
+
from flwr.common.logger import log, warn_unsupported_feature
|
|
36
24
|
from flwr.common.object_ref import load_app
|
|
37
|
-
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
|
38
|
-
from flwr.proto.run_pb2 import ( # pylint: disable=E0611
|
|
39
|
-
CreateRunRequest,
|
|
40
|
-
CreateRunResponse,
|
|
41
|
-
)
|
|
42
25
|
|
|
43
26
|
from .driver import Driver
|
|
44
|
-
from .driver.grpc_driver import GrpcDriver
|
|
45
27
|
from .server_app import LoadServerAppError, ServerApp
|
|
46
28
|
|
|
47
29
|
|
|
@@ -87,221 +69,9 @@ def run(
|
|
|
87
69
|
# pylint: disable-next=too-many-branches,too-many-statements,too-many-locals
|
|
88
70
|
def run_server_app() -> None:
|
|
89
71
|
"""Run Flower server app."""
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# Check if the server app reference is passed.
|
|
95
|
-
# Since Flower 1.11, passing a reference is not allowed.
|
|
96
|
-
app_path: Optional[str] = args.app
|
|
97
|
-
# If the provided app_path doesn't exist, and contains a ":",
|
|
98
|
-
# it is likely to be a server app reference instead of a path.
|
|
99
|
-
if app_path is not None and not Path(app_path).exists() and ":" in app_path:
|
|
100
|
-
sys.exit(
|
|
101
|
-
"It appears you've passed a reference like `server:app`.\n\n"
|
|
102
|
-
"Note that since version `1.11.0`, `flower-server-app` no longer supports "
|
|
103
|
-
"passing a reference to a `ServerApp` attribute. Instead, you need to pass "
|
|
104
|
-
"the path to Flower app via the argument `--app`. This is the path to a "
|
|
105
|
-
"directory containing a `pyproject.toml`. You can create a valid Flower "
|
|
106
|
-
"app by executing `flwr new` and following the prompt."
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
if args.server != SERVERAPPIO_API_DEFAULT_ADDRESS:
|
|
110
|
-
warn = "Passing flag --server is deprecated. Use --superlink instead."
|
|
111
|
-
warn_deprecated_feature(warn)
|
|
112
|
-
|
|
113
|
-
if args.superlink != SERVERAPPIO_API_DEFAULT_ADDRESS:
|
|
114
|
-
# if `--superlink` also passed, then
|
|
115
|
-
# warn user that this argument overrides what was passed with `--server`
|
|
116
|
-
log(
|
|
117
|
-
WARN,
|
|
118
|
-
"Both `--server` and `--superlink` were passed. "
|
|
119
|
-
"`--server` will be ignored. Connecting to the "
|
|
120
|
-
"SuperLink ServerAppIo API at %s.",
|
|
121
|
-
args.superlink,
|
|
122
|
-
)
|
|
123
|
-
else:
|
|
124
|
-
args.superlink = args.server
|
|
125
|
-
|
|
126
|
-
update_console_handler(
|
|
127
|
-
level=DEBUG if args.verbose else INFO,
|
|
128
|
-
timestamps=args.verbose,
|
|
129
|
-
colored=True,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
# Obtain certificates
|
|
133
|
-
if args.insecure:
|
|
134
|
-
if args.root_certificates is not None:
|
|
135
|
-
sys.exit(
|
|
136
|
-
"Conflicting options: The '--insecure' flag disables HTTPS, "
|
|
137
|
-
"but '--root-certificates' was also specified. Please remove "
|
|
138
|
-
"the '--root-certificates' option when running in insecure mode, "
|
|
139
|
-
"or omit '--insecure' to use HTTPS."
|
|
140
|
-
)
|
|
141
|
-
log(
|
|
142
|
-
WARN,
|
|
143
|
-
"Option `--insecure` was set. "
|
|
144
|
-
"Starting insecure HTTP client connected to %s.",
|
|
145
|
-
args.superlink,
|
|
146
|
-
)
|
|
147
|
-
root_certificates = None
|
|
148
|
-
else:
|
|
149
|
-
# Load the certificates if provided, or load the system certificates
|
|
150
|
-
cert_path = args.root_certificates
|
|
151
|
-
if cert_path is None:
|
|
152
|
-
root_certificates = None
|
|
153
|
-
else:
|
|
154
|
-
root_certificates = Path(cert_path).read_bytes()
|
|
155
|
-
log(
|
|
156
|
-
DEBUG,
|
|
157
|
-
"Starting secure HTTPS client connected to %s "
|
|
158
|
-
"with the following certificates: %s.",
|
|
159
|
-
args.superlink,
|
|
160
|
-
cert_path,
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
if not (app_path is None) ^ (args.run_id is None):
|
|
164
|
-
raise sys.exit(
|
|
165
|
-
"Please provide either a Flower App path or a Run ID, but not both. "
|
|
166
|
-
"For more details, use: ``flower-server-app -h``"
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
# Initialize GrpcDriver
|
|
170
|
-
if app_path is None:
|
|
171
|
-
# User provided `--run-id`, but not `app_dir`
|
|
172
|
-
driver = GrpcDriver(
|
|
173
|
-
serverappio_service_address=args.superlink,
|
|
174
|
-
root_certificates=root_certificates,
|
|
175
|
-
)
|
|
176
|
-
flwr_dir = get_flwr_dir(args.flwr_dir)
|
|
177
|
-
driver.set_run(args.run_id)
|
|
178
|
-
run_ = driver.run
|
|
179
|
-
if not run_.fab_hash:
|
|
180
|
-
raise ValueError("FAB hash not provided.")
|
|
181
|
-
fab_req = GetFabRequest(hash_str=run_.fab_hash)
|
|
182
|
-
# pylint: disable-next=W0212
|
|
183
|
-
fab_res: GetFabResponse = driver._stub.GetFab(fab_req)
|
|
184
|
-
if fab_res.fab.hash_str != run_.fab_hash:
|
|
185
|
-
raise ValueError("FAB hashes don't match.")
|
|
186
|
-
install_from_fab(fab_res.fab.content, flwr_dir, True)
|
|
187
|
-
fab_id, fab_version = get_fab_metadata(fab_res.fab.content)
|
|
188
|
-
|
|
189
|
-
app_path = str(get_project_dir(fab_id, fab_version, run_.fab_hash, flwr_dir))
|
|
190
|
-
config = get_project_config(app_path)
|
|
191
|
-
run_id = run_.run_id
|
|
192
|
-
else:
|
|
193
|
-
# User provided `app_dir`, but not `--run-id`
|
|
194
|
-
# Create run if run_id is not provided
|
|
195
|
-
driver = GrpcDriver(
|
|
196
|
-
serverappio_service_address=args.superlink,
|
|
197
|
-
root_certificates=root_certificates,
|
|
198
|
-
)
|
|
199
|
-
# Load config from the project directory
|
|
200
|
-
config = get_project_config(app_path)
|
|
201
|
-
fab_version, fab_id = get_metadata_from_config(config)
|
|
202
|
-
|
|
203
|
-
# Create run
|
|
204
|
-
req = CreateRunRequest(fab_id=fab_id, fab_version=fab_version)
|
|
205
|
-
res: CreateRunResponse = driver._stub.CreateRun(req) # pylint: disable=W0212
|
|
206
|
-
# Fetch full `Run` using `run_id`
|
|
207
|
-
driver.set_run(res.run_id) # pylint: disable=W0212
|
|
208
|
-
run_id = res.run_id
|
|
209
|
-
|
|
210
|
-
# Obtain server app reference and the run config
|
|
211
|
-
server_app_attr = config["tool"]["flwr"]["app"]["components"]["serverapp"]
|
|
212
|
-
server_app_run_config = get_fused_config_from_dir(
|
|
213
|
-
Path(app_path), driver.run.override_config
|
|
72
|
+
warn_unsupported_feature(
|
|
73
|
+
"The command `flower-server-app` is deprecated and no longer in use. "
|
|
74
|
+
"Use the `flwr-serverapp` exclusively instead."
|
|
214
75
|
)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
log(
|
|
219
|
-
DEBUG,
|
|
220
|
-
"root_certificates: `%s`",
|
|
221
|
-
root_certificates,
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
# Initialize Context
|
|
225
|
-
context = Context(
|
|
226
|
-
run_id=run_id,
|
|
227
|
-
node_id=0,
|
|
228
|
-
node_config={},
|
|
229
|
-
state=RecordSet(),
|
|
230
|
-
run_config=server_app_run_config,
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
# Run the ServerApp with the Driver
|
|
234
|
-
run(
|
|
235
|
-
driver=driver,
|
|
236
|
-
context=context,
|
|
237
|
-
server_app_dir=app_path,
|
|
238
|
-
server_app_attr=server_app_attr,
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
# Clean up
|
|
242
|
-
driver.close()
|
|
243
|
-
|
|
244
|
-
event(EventType.RUN_SERVER_APP_LEAVE)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def _parse_args_run_server_app() -> argparse.ArgumentParser:
|
|
248
|
-
"""Parse flower-server-app command line arguments."""
|
|
249
|
-
parser = argparse.ArgumentParser(
|
|
250
|
-
description="Start a Flower server app",
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
parser.add_argument(
|
|
254
|
-
"app",
|
|
255
|
-
nargs="?",
|
|
256
|
-
default=None,
|
|
257
|
-
help="Load and run the `ServerApp` from the specified Flower App path. "
|
|
258
|
-
"The `pyproject.toml` file must be located in the root of this path.",
|
|
259
|
-
)
|
|
260
|
-
parser.add_argument(
|
|
261
|
-
"--insecure",
|
|
262
|
-
action="store_true",
|
|
263
|
-
help="Run the `ServerApp` without HTTPS. By default, the app runs with "
|
|
264
|
-
"HTTPS enabled. Use this flag only if you understand the risks.",
|
|
265
|
-
)
|
|
266
|
-
parser.add_argument(
|
|
267
|
-
"--verbose",
|
|
268
|
-
action="store_true",
|
|
269
|
-
help="Set the logging to `DEBUG`.",
|
|
270
|
-
)
|
|
271
|
-
parser.add_argument(
|
|
272
|
-
"--root-certificates",
|
|
273
|
-
metavar="ROOT_CERT",
|
|
274
|
-
type=str,
|
|
275
|
-
help="Specifies the path to the PEM-encoded root certificate file for "
|
|
276
|
-
"establishing secure HTTPS connections.",
|
|
277
|
-
)
|
|
278
|
-
parser.add_argument(
|
|
279
|
-
"--server",
|
|
280
|
-
default=SERVERAPPIO_API_DEFAULT_ADDRESS,
|
|
281
|
-
help="Server address",
|
|
282
|
-
)
|
|
283
|
-
parser.add_argument(
|
|
284
|
-
"--superlink",
|
|
285
|
-
default=SERVERAPPIO_API_DEFAULT_ADDRESS,
|
|
286
|
-
help="SuperLink ServerAppIo API (gRPC-rere) address "
|
|
287
|
-
"(IPv4, IPv6, or a domain name)",
|
|
288
|
-
)
|
|
289
|
-
parser.add_argument(
|
|
290
|
-
"--run-id",
|
|
291
|
-
default=None,
|
|
292
|
-
type=int,
|
|
293
|
-
help="The identifier of the run.",
|
|
294
|
-
)
|
|
295
|
-
parser.add_argument(
|
|
296
|
-
"--flwr-dir",
|
|
297
|
-
default=None,
|
|
298
|
-
help="""The path containing installed Flower Apps.
|
|
299
|
-
By default, this value is equal to:
|
|
300
|
-
|
|
301
|
-
- `$FLWR_HOME/` if `$FLWR_HOME` is defined
|
|
302
|
-
- `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined
|
|
303
|
-
- `$HOME/.flwr/` in all other cases
|
|
304
|
-
""",
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
return parser
|
|
76
|
+
log(ERROR, "`flower-server-app` used.")
|
|
77
|
+
sys.exit()
|
flwr/server/serverapp/app.py
CHANGED
|
@@ -30,7 +30,11 @@ from flwr.common.config import (
|
|
|
30
30
|
get_project_config,
|
|
31
31
|
get_project_dir,
|
|
32
32
|
)
|
|
33
|
-
from flwr.common.constant import
|
|
33
|
+
from flwr.common.constant import (
|
|
34
|
+
SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
35
|
+
Status,
|
|
36
|
+
SubStatus,
|
|
37
|
+
)
|
|
34
38
|
from flwr.common.logger import (
|
|
35
39
|
log,
|
|
36
40
|
mirror_output_to_queue,
|
|
@@ -62,33 +66,18 @@ def flwr_serverapp() -> None:
|
|
|
62
66
|
log_queue: Queue[Optional[str]] = Queue()
|
|
63
67
|
mirror_output_to_queue(log_queue)
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
description="Run a Flower ServerApp",
|
|
67
|
-
)
|
|
68
|
-
parser.add_argument(
|
|
69
|
-
"--superlink",
|
|
70
|
-
type=str,
|
|
71
|
-
help="Address of SuperLink's ServerAppIo API",
|
|
72
|
-
)
|
|
73
|
-
parser.add_argument(
|
|
74
|
-
"--run-once",
|
|
75
|
-
action="store_true",
|
|
76
|
-
help="When set, this process will start a single ServerApp for a pending Run. "
|
|
77
|
-
"If there is no pending Run, the process will exit.",
|
|
78
|
-
)
|
|
79
|
-
add_args_flwr_app_common(parser=parser)
|
|
80
|
-
args = parser.parse_args()
|
|
69
|
+
args = _parse_args_run_flwr_serverapp().parse_args()
|
|
81
70
|
|
|
82
71
|
log(INFO, "Starting Flower ServerApp")
|
|
83
|
-
certificates = try_obtain_root_certificates(args, args.
|
|
72
|
+
certificates = try_obtain_root_certificates(args, args.serverappio_api_address)
|
|
84
73
|
|
|
85
74
|
log(
|
|
86
75
|
DEBUG,
|
|
87
76
|
"Starting isolated `ServerApp` connected to SuperLink's ServerAppIo API at %s",
|
|
88
|
-
args.
|
|
77
|
+
args.serverappio_api_address,
|
|
89
78
|
)
|
|
90
79
|
run_serverapp(
|
|
91
|
-
|
|
80
|
+
serverappio_api_address=args.serverappio_api_address,
|
|
92
81
|
log_queue=log_queue,
|
|
93
82
|
run_once=args.run_once,
|
|
94
83
|
flwr_dir=args.flwr_dir,
|
|
@@ -100,7 +89,7 @@ def flwr_serverapp() -> None:
|
|
|
100
89
|
|
|
101
90
|
|
|
102
91
|
def run_serverapp( # pylint: disable=R0914, disable=W0212
|
|
103
|
-
|
|
92
|
+
serverappio_api_address: str,
|
|
104
93
|
log_queue: Queue[Optional[str]],
|
|
105
94
|
run_once: bool,
|
|
106
95
|
flwr_dir: Optional[str] = None,
|
|
@@ -108,7 +97,7 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
|
|
|
108
97
|
) -> None:
|
|
109
98
|
"""Run Flower ServerApp process."""
|
|
110
99
|
driver = GrpcDriver(
|
|
111
|
-
serverappio_service_address=
|
|
100
|
+
serverappio_service_address=serverappio_api_address,
|
|
112
101
|
root_certificates=certificates,
|
|
113
102
|
)
|
|
114
103
|
|
|
@@ -212,3 +201,25 @@ def run_serverapp( # pylint: disable=R0914, disable=W0212
|
|
|
212
201
|
# Stop the loop if `flwr-serverapp` is expected to process a single run
|
|
213
202
|
if run_once:
|
|
214
203
|
break
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _parse_args_run_flwr_serverapp() -> argparse.ArgumentParser:
|
|
207
|
+
"""Parse flwr-serverapp command line arguments."""
|
|
208
|
+
parser = argparse.ArgumentParser(
|
|
209
|
+
description="Run a Flower ServerApp",
|
|
210
|
+
)
|
|
211
|
+
parser.add_argument(
|
|
212
|
+
"--serverappio-api-address",
|
|
213
|
+
default=SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS,
|
|
214
|
+
type=str,
|
|
215
|
+
help="Address of SuperLink's ServerAppIo API (IPv4, IPv6, or a domain name)."
|
|
216
|
+
f"By default, it is set to {SERVERAPPIO_API_DEFAULT_CLIENT_ADDRESS}.",
|
|
217
|
+
)
|
|
218
|
+
parser.add_argument(
|
|
219
|
+
"--run-once",
|
|
220
|
+
action="store_true",
|
|
221
|
+
help="When set, this process will start a single ServerApp for a pending Run. "
|
|
222
|
+
"If there is no pending Run, the process will exit.",
|
|
223
|
+
)
|
|
224
|
+
add_args_flwr_app_common(parser=parser)
|
|
225
|
+
return parser
|
|
@@ -40,6 +40,8 @@ from .utils import (
|
|
|
40
40
|
generate_rand_int_from_bytes,
|
|
41
41
|
has_valid_sub_status,
|
|
42
42
|
is_valid_transition,
|
|
43
|
+
verify_found_taskres,
|
|
44
|
+
verify_taskins_ids,
|
|
43
45
|
)
|
|
44
46
|
|
|
45
47
|
|
|
@@ -67,12 +69,13 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
67
69
|
self.federation_options: dict[int, ConfigsRecord] = {}
|
|
68
70
|
self.task_ins_store: dict[UUID, TaskIns] = {}
|
|
69
71
|
self.task_res_store: dict[UUID, TaskRes] = {}
|
|
72
|
+
self.task_ins_id_to_task_res_id: dict[UUID, UUID] = {}
|
|
70
73
|
|
|
71
74
|
self.node_public_keys: set[bytes] = set()
|
|
72
75
|
self.server_public_key: Optional[bytes] = None
|
|
73
76
|
self.server_private_key: Optional[bytes] = None
|
|
74
77
|
|
|
75
|
-
self.lock = threading.
|
|
78
|
+
self.lock = threading.RLock()
|
|
76
79
|
|
|
77
80
|
def store_task_ins(self, task_ins: TaskIns) -> Optional[UUID]:
|
|
78
81
|
"""Store one TaskIns."""
|
|
@@ -222,42 +225,50 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
222
225
|
task_res.task_id = str(task_id)
|
|
223
226
|
with self.lock:
|
|
224
227
|
self.task_res_store[task_id] = task_res
|
|
228
|
+
self.task_ins_id_to_task_res_id[UUID(task_ins_id)] = task_id
|
|
225
229
|
|
|
226
230
|
# Return the new task_id
|
|
227
231
|
return task_id
|
|
228
232
|
|
|
229
233
|
def get_task_res(self, task_ids: set[UUID]) -> list[TaskRes]:
|
|
230
|
-
"""Get
|
|
234
|
+
"""Get TaskRes for the given TaskIns IDs."""
|
|
235
|
+
ret: dict[UUID, TaskRes] = {}
|
|
236
|
+
|
|
231
237
|
with self.lock:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
238
|
+
current = time.time()
|
|
239
|
+
|
|
240
|
+
# Verify TaskIns IDs
|
|
241
|
+
ret = verify_taskins_ids(
|
|
242
|
+
inquired_taskins_ids=task_ids,
|
|
243
|
+
found_taskins_dict=self.task_ins_store,
|
|
244
|
+
current_time=current,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Find all TaskRes
|
|
248
|
+
task_res_found: list[TaskRes] = []
|
|
249
|
+
for task_id in task_ids:
|
|
250
|
+
# If TaskRes exists and is not delivered, add it to the list
|
|
251
|
+
if task_res_id := self.task_ins_id_to_task_res_id.get(task_id):
|
|
252
|
+
task_res = self.task_res_store[task_res_id]
|
|
253
|
+
if task_res.task.delivered_at == "":
|
|
254
|
+
task_res_found.append(task_res)
|
|
255
|
+
tmp_ret_dict = verify_found_taskres(
|
|
256
|
+
inquired_taskins_ids=task_ids,
|
|
257
|
+
found_taskins_dict=self.task_ins_store,
|
|
258
|
+
found_taskres_list=task_res_found,
|
|
259
|
+
current_time=current,
|
|
260
|
+
)
|
|
261
|
+
ret.update(tmp_ret_dict)
|
|
262
|
+
|
|
263
|
+
# Mark existing TaskRes to be returned as delivered
|
|
255
264
|
delivered_at = now().isoformat()
|
|
256
|
-
for task_res in
|
|
265
|
+
for task_res in task_res_found:
|
|
257
266
|
task_res.task.delivered_at = delivered_at
|
|
258
267
|
|
|
259
|
-
#
|
|
260
|
-
|
|
268
|
+
# Cleanup
|
|
269
|
+
self._force_delete_tasks_by_ids(set(ret.keys()))
|
|
270
|
+
|
|
271
|
+
return list(ret.values())
|
|
261
272
|
|
|
262
273
|
def delete_tasks(self, task_ids: set[UUID]) -> None:
|
|
263
274
|
"""Delete all delivered TaskIns/TaskRes pairs."""
|
|
@@ -278,9 +289,25 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
278
289
|
|
|
279
290
|
for task_id in task_ins_to_be_deleted:
|
|
280
291
|
del self.task_ins_store[task_id]
|
|
292
|
+
del self.task_ins_id_to_task_res_id[task_id]
|
|
281
293
|
for task_id in task_res_to_be_deleted:
|
|
282
294
|
del self.task_res_store[task_id]
|
|
283
295
|
|
|
296
|
+
def _force_delete_tasks_by_ids(self, task_ids: set[UUID]) -> None:
|
|
297
|
+
"""Delete tasks based on a set of TaskIns IDs."""
|
|
298
|
+
if not task_ids:
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
with self.lock:
|
|
302
|
+
for task_id in task_ids:
|
|
303
|
+
# Delete TaskIns
|
|
304
|
+
if task_id in self.task_ins_store:
|
|
305
|
+
del self.task_ins_store[task_id]
|
|
306
|
+
# Delete TaskRes
|
|
307
|
+
if task_id in self.task_ins_id_to_task_res_id:
|
|
308
|
+
task_res_id = self.task_ins_id_to_task_res_id.pop(task_id)
|
|
309
|
+
del self.task_res_store[task_res_id]
|
|
310
|
+
|
|
284
311
|
def num_task_ins(self) -> int:
|
|
285
312
|
"""Calculate the number of task_ins in store.
|
|
286
313
|
|
|
@@ -101,13 +101,27 @@ class LinkState(abc.ABC): # pylint: disable=R0904
|
|
|
101
101
|
|
|
102
102
|
@abc.abstractmethod
|
|
103
103
|
def get_task_res(self, task_ids: set[UUID]) -> list[TaskRes]:
|
|
104
|
-
"""Get TaskRes for
|
|
104
|
+
"""Get TaskRes for the given TaskIns IDs.
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
This method is typically called by the ServerAppIo API to obtain
|
|
107
|
+
results (TaskRes) for previously scheduled instructions (TaskIns).
|
|
108
|
+
For each task_id provided, this method returns one of the following responses:
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
- An error TaskRes if the corresponding TaskIns does not exist or has expired.
|
|
111
|
+
- An error TaskRes if the corresponding TaskRes exists but has expired.
|
|
112
|
+
- The valid TaskRes if the TaskIns has a corresponding valid TaskRes.
|
|
113
|
+
- Nothing if the TaskIns is still valid and waiting for a TaskRes.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
task_ids : set[UUID]
|
|
118
|
+
A set of TaskIns IDs for which to retrieve results (TaskRes).
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
list[TaskRes]
|
|
123
|
+
A list of TaskRes corresponding to the given task IDs. If no
|
|
124
|
+
TaskRes could be found for any of the task IDs, an empty list is returned.
|
|
111
125
|
"""
|
|
112
126
|
|
|
113
127
|
@abc.abstractmethod
|