tracdap-runtime 0.8.0rc2__py3-none-any.whl → 0.9.0b1__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.
- tracdap/rt/_impl/core/data.py +578 -33
- tracdap/rt/_impl/core/repos.py +7 -0
- tracdap/rt/_impl/core/storage.py +10 -3
- tracdap/rt/_impl/core/util.py +54 -11
- tracdap/rt/_impl/exec/dev_mode.py +122 -100
- tracdap/rt/_impl/exec/engine.py +178 -109
- tracdap/rt/_impl/exec/functions.py +218 -257
- tracdap/rt/_impl/exec/graph.py +140 -125
- tracdap/rt/_impl/exec/graph_builder.py +411 -449
- tracdap/rt/_impl/grpc/codec.py +4 -2
- tracdap/rt/_impl/grpc/server.py +7 -7
- tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.py +25 -18
- tracdap/rt/_impl/grpc/tracdap/api/internal/runtime_pb2.pyi +27 -9
- tracdap/rt/_impl/grpc/tracdap/metadata/common_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/config_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/custom_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/data_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/file_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/flow_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.py +67 -63
- tracdap/rt/_impl/grpc/tracdap/metadata/job_pb2.pyi +11 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/model_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/object_id_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/object_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/resource_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/search_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/storage_pb2.py +11 -9
- tracdap/rt/_impl/grpc/tracdap/metadata/storage_pb2.pyi +11 -2
- tracdap/rt/_impl/grpc/tracdap/metadata/tag_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/tag_update_pb2.py +1 -1
- tracdap/rt/_impl/grpc/tracdap/metadata/type_pb2.py +1 -1
- tracdap/rt/_impl/runtime.py +8 -0
- tracdap/rt/_plugins/repo_git.py +56 -11
- tracdap/rt/_version.py +1 -1
- tracdap/rt/config/__init__.py +6 -6
- tracdap/rt/config/common.py +5 -0
- tracdap/rt/config/job.py +13 -3
- tracdap/rt/config/result.py +8 -4
- tracdap/rt/config/runtime.py +2 -0
- tracdap/rt/metadata/__init__.py +37 -36
- tracdap/rt/metadata/job.py +2 -0
- tracdap/rt/metadata/storage.py +9 -0
- {tracdap_runtime-0.8.0rc2.dist-info → tracdap_runtime-0.9.0b1.dist-info}/METADATA +3 -1
- {tracdap_runtime-0.8.0rc2.dist-info → tracdap_runtime-0.9.0b1.dist-info}/RECORD +47 -47
- {tracdap_runtime-0.8.0rc2.dist-info → tracdap_runtime-0.9.0b1.dist-info}/WHEEL +1 -1
- {tracdap_runtime-0.8.0rc2.dist-info → tracdap_runtime-0.9.0b1.dist-info}/licenses/LICENSE +0 -0
- {tracdap_runtime-0.8.0rc2.dist-info → tracdap_runtime-0.9.0b1.dist-info}/top_level.txt +0 -0
tracdap/rt/_impl/exec/engine.py
CHANGED
@@ -91,11 +91,32 @@ class _JobResultSpec:
|
|
91
91
|
result_format: str = None
|
92
92
|
|
93
93
|
|
94
|
+
@dc.dataclass
|
95
|
+
class _JobLog:
|
96
|
+
|
97
|
+
log_init: "dc.InitVar[tp.Optional[_JobLog]]" = None
|
98
|
+
|
99
|
+
log_file_needed: bool = True
|
100
|
+
|
101
|
+
log_buffer: io.BytesIO = None
|
102
|
+
log_provider: _logging.LogProvider = None
|
103
|
+
|
104
|
+
def __post_init__(self, log_init):
|
105
|
+
|
106
|
+
if log_init is not None:
|
107
|
+
self.log_provider = log_init.log_provider
|
108
|
+
self.log_file_needed = False
|
109
|
+
elif self.log_file_needed:
|
110
|
+
self.log_buffer = io.BytesIO()
|
111
|
+
self.log_provider = _logging.job_log_provider(self.log_buffer)
|
112
|
+
else:
|
113
|
+
self.log_provider = _logging.LogProvider()
|
114
|
+
|
115
|
+
|
94
116
|
@dc.dataclass
|
95
117
|
class _JobState:
|
96
118
|
|
97
119
|
job_id: _meta.TagHeader
|
98
|
-
log_init: dc.InitVar[tp.Optional[_logging.LogProvider]] = None
|
99
120
|
|
100
121
|
actor_id: _actors.ActorId = None
|
101
122
|
monitors: tp.List[_actors.ActorId] = dc.field(default_factory=list)
|
@@ -107,19 +128,7 @@ class _JobState:
|
|
107
128
|
parent_key: str = None
|
108
129
|
result_spec: _JobResultSpec = None
|
109
130
|
|
110
|
-
|
111
|
-
log_provider: _logging.LogProvider = None
|
112
|
-
log: _logging.Logger = None
|
113
|
-
|
114
|
-
def __post_init__(self, log_init):
|
115
|
-
|
116
|
-
if isinstance(self.log, _logging.LogProvider):
|
117
|
-
self.log_provider = log_init
|
118
|
-
else:
|
119
|
-
self.log_buffer = io.BytesIO()
|
120
|
-
self.log_provider = _logging.job_log_provider(self.log_buffer)
|
121
|
-
|
122
|
-
self.log = self.log_provider.logger_for_class(TracEngine)
|
131
|
+
job_log: _JobLog = None
|
123
132
|
|
124
133
|
|
125
134
|
class TracEngine(_actors.Actor):
|
@@ -194,16 +203,19 @@ class TracEngine(_actors.Actor):
|
|
194
203
|
job_result_format: str):
|
195
204
|
|
196
205
|
job_key = _util.object_key(job_config.jobId)
|
197
|
-
job_state = _JobState(job_config.jobId)
|
198
206
|
|
199
|
-
|
207
|
+
self._log.info(f"Received a new job: [{job_key}]")
|
200
208
|
|
201
209
|
result_needed = bool(job_result_dir)
|
202
210
|
result_spec = _JobResultSpec(result_needed, job_result_dir, job_result_format)
|
211
|
+
job_log = _JobLog(log_file_needed=result_needed)
|
212
|
+
|
213
|
+
job_state = _JobState(job_config.jobId)
|
214
|
+
job_state.job_log = job_log
|
203
215
|
|
204
216
|
job_processor = JobProcessor(
|
205
|
-
self._sys_config, self._models, self._storage,
|
206
|
-
job_key, job_config, graph_spec=None)
|
217
|
+
self._sys_config, self._models, self._storage,
|
218
|
+
job_key, job_config, graph_spec=None, job_log=job_state.job_log)
|
207
219
|
|
208
220
|
job_actor_id = self.actors().spawn(job_processor)
|
209
221
|
|
@@ -231,13 +243,18 @@ class TracEngine(_actors.Actor):
|
|
231
243
|
|
232
244
|
child_key = _util.object_key(child_id)
|
233
245
|
|
246
|
+
self._log.info(f"Received a child job: [{child_key}] for parent [{parent_key}]")
|
247
|
+
|
248
|
+
child_job_log = _JobLog(parent_state.job_log)
|
249
|
+
|
234
250
|
child_processor = JobProcessor(
|
235
|
-
self._sys_config, self._models, self._storage,
|
236
|
-
child_key, None, graph_spec=child_graph)
|
251
|
+
self._sys_config, self._models, self._storage,
|
252
|
+
child_key, None, graph_spec=child_graph, job_log=child_job_log)
|
237
253
|
|
238
254
|
child_actor_id = self.actors().spawn(child_processor)
|
239
255
|
|
240
|
-
child_state = _JobState(child_id
|
256
|
+
child_state = _JobState(child_id)
|
257
|
+
child_state.job_log = child_job_log
|
241
258
|
child_state.actor_id = child_actor_id
|
242
259
|
child_state.monitors.append(monitor_id)
|
243
260
|
child_state.parent_key = parent_key
|
@@ -265,9 +282,9 @@ class TracEngine(_actors.Actor):
|
|
265
282
|
self._log.warning(f"Ignoring [job_succeeded] message, job [{job_key}] has already completed")
|
266
283
|
return
|
267
284
|
|
268
|
-
|
269
|
-
job_state.log.info(f"Recording job as successful: {job_key}")
|
285
|
+
self._log.info(f"Marking job as successful: {job_key}")
|
270
286
|
|
287
|
+
job_state = self._jobs[job_key]
|
271
288
|
job_state.job_result = job_result
|
272
289
|
|
273
290
|
for monitor_id in job_state.monitors:
|
@@ -276,36 +293,30 @@ class TracEngine(_actors.Actor):
|
|
276
293
|
self._finalize_job(job_key)
|
277
294
|
|
278
295
|
@_actors.Message
|
279
|
-
def job_failed(self, job_key: str, error: Exception):
|
296
|
+
def job_failed(self, job_key: str, error: Exception, job_result: tp.Optional[_cfg.JobResult] = None):
|
280
297
|
|
281
298
|
# Ignore duplicate messages from the job processor (can happen in unusual error cases)
|
282
299
|
if job_key not in self._jobs:
|
283
300
|
self._log.warning(f"Ignoring [job_failed] message, job [{job_key}] has already completed")
|
284
301
|
return
|
285
302
|
|
286
|
-
|
287
|
-
job_state.log.error(f"Recording job as failed: {job_key}")
|
288
|
-
|
289
|
-
job_state.job_error = error
|
290
|
-
|
291
|
-
# Create a failed result so there is something to report
|
292
|
-
result_id = job_state.job_config.resultMapping.get("trac_job_result")
|
303
|
+
self._log.error(f"Marking job as failed: {job_key}")
|
293
304
|
|
294
|
-
|
295
|
-
|
296
|
-
job_state.job_result = _cfg.JobResult(
|
297
|
-
jobId=job_state.job_id,
|
298
|
-
statusCode=_meta.JobStatusCode.FAILED,
|
299
|
-
statusMessage=str(error))
|
305
|
+
job_state = self._jobs[job_key]
|
300
306
|
|
307
|
+
# Build a failed result if none is supplied by the job processor (should not normally happen)
|
308
|
+
# In this case, no job log will be included in the output
|
309
|
+
if job_result is None and job_state.job_config is not None:
|
310
|
+
job_id = job_state.job_id
|
311
|
+
result_id = job_state.job_config.resultId
|
301
312
|
result_def = _meta.ResultDefinition()
|
302
313
|
result_def.jobId = _util.selector_for(job_state.job_id)
|
303
314
|
result_def.statusCode = _meta.JobStatusCode.FAILED
|
315
|
+
result_def.statusMessage = str(error)
|
316
|
+
job_result = _cfg.JobResult(job_id, result_id, result_def)
|
304
317
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
job_state.job_result.results[result_key] = result_obj
|
318
|
+
job_state.job_result = job_result
|
319
|
+
job_state.job_error = error
|
309
320
|
|
310
321
|
for monitor_id in job_state.monitors:
|
311
322
|
self.actors().send(monitor_id, "job_failed", error)
|
@@ -323,10 +334,6 @@ class TracEngine(_actors.Actor):
|
|
323
334
|
|
324
335
|
# Record output metadata if required (not needed for local runs or when using API server)
|
325
336
|
if job_state.parent_key is None and job_state.result_spec.save_result:
|
326
|
-
|
327
|
-
if "trac_job_log_file" in job_state.job_config.resultMapping:
|
328
|
-
self._save_job_log_file(job_key, job_state)
|
329
|
-
|
330
337
|
self._save_job_result(job_key, job_state)
|
331
338
|
|
332
339
|
# Stop any monitors that were created directly by the engine
|
@@ -341,37 +348,6 @@ class TracEngine(_actors.Actor):
|
|
341
348
|
self.actors().stop(job_state.actor_id)
|
342
349
|
job_state.actor_id = None
|
343
350
|
|
344
|
-
def _save_job_log_file(self, job_key: str, job_state: _JobState):
|
345
|
-
|
346
|
-
self._log.info(f"Saving job log file for [{job_key}]")
|
347
|
-
|
348
|
-
# Saving log files could go into a separate actor, perhaps a job monitor along with _save_job_result()
|
349
|
-
|
350
|
-
file_id = job_state.job_config.resultMapping["trac_job_log_file"]
|
351
|
-
storage_id = job_state.job_config.resultMapping["trac_job_log_file:STORAGE"]
|
352
|
-
|
353
|
-
file_type = _meta.FileType("TXT", "text/plain")
|
354
|
-
file_def, storage_def = _graph.GraphBuilder.build_output_file_and_storage(
|
355
|
-
"trac_job_log_file", file_type,
|
356
|
-
self._sys_config, job_state.job_config)
|
357
|
-
|
358
|
-
storage_item = storage_def.dataItems[file_def.dataItem].incarnations[0].copies[0]
|
359
|
-
storage = self._storage.get_file_storage(storage_item.storageKey)
|
360
|
-
|
361
|
-
with storage.write_byte_stream(storage_item.storagePath) as stream:
|
362
|
-
stream.write(job_state.log_buffer.getbuffer())
|
363
|
-
file_def.size = stream.tell()
|
364
|
-
|
365
|
-
result_id = job_state.job_config.resultMapping["trac_job_result"]
|
366
|
-
result_def = job_state.job_result.results[_util.object_key(result_id)].result
|
367
|
-
result_def.logFileId = _util.selector_for(file_id)
|
368
|
-
|
369
|
-
file_obj = _meta.ObjectDefinition(objectType=_meta.ObjectType.FILE, file=file_def)
|
370
|
-
storage_obj = _meta.ObjectDefinition(objectType=_meta.ObjectType.STORAGE, storage=storage_def)
|
371
|
-
|
372
|
-
job_state.job_result.results[_util.object_key(file_id)] = file_obj
|
373
|
-
job_state.job_result.results[_util.object_key(storage_id)] = storage_obj
|
374
|
-
|
375
351
|
def _save_job_result(self, job_key: str, job_state: _JobState):
|
376
352
|
|
377
353
|
self._log.info(f"Saving job result for [{job_key}]")
|
@@ -398,24 +374,28 @@ class TracEngine(_actors.Actor):
|
|
398
374
|
|
399
375
|
job_result = _cfg.JobResult()
|
400
376
|
job_result.jobId = job_state.job_id
|
377
|
+
job_result.resultId = job_state.job_config.resultId
|
378
|
+
job_result.result = _meta.ResultDefinition()
|
379
|
+
job_result.result.jobId = _util.selector_for(job_state.job_id)
|
401
380
|
|
402
381
|
if job_state.actor_id is not None:
|
403
|
-
job_result.statusCode = _meta.JobStatusCode.RUNNING
|
382
|
+
job_result.result.statusCode = _meta.JobStatusCode.RUNNING
|
404
383
|
|
405
384
|
elif job_state.job_result is not None:
|
406
|
-
job_result.statusCode = job_state.job_result.statusCode
|
407
|
-
job_result.statusMessage = job_state.job_result.statusMessage
|
385
|
+
job_result.result.statusCode = job_state.job_result.result.statusCode
|
386
|
+
job_result.result.statusMessage = job_state.job_result.result.statusMessage
|
408
387
|
if details:
|
409
|
-
job_result.
|
388
|
+
job_result.objectIds = job_state.job_result.objectIds or list()
|
389
|
+
job_result.objects = job_state.job_result.objects or dict()
|
410
390
|
|
411
391
|
elif job_state.job_error is not None:
|
412
|
-
job_result.statusCode = _meta.JobStatusCode.FAILED
|
413
|
-
job_result.statusMessage = str(job_state.job_error.args[0])
|
392
|
+
job_result.result.statusCode = _meta.JobStatusCode.FAILED
|
393
|
+
job_result.result.statusMessage = str(job_state.job_error.args[0])
|
414
394
|
|
415
395
|
else:
|
416
396
|
# Alternatively return UNKNOWN status or throw an error here
|
417
|
-
job_result.statusCode = _meta.JobStatusCode.FAILED
|
418
|
-
job_result.statusMessage = "No details available"
|
397
|
+
job_result.result.statusCode = _meta.JobStatusCode.FAILED
|
398
|
+
job_result.result.statusMessage = "No details available"
|
419
399
|
|
420
400
|
return job_result
|
421
401
|
|
@@ -458,8 +438,9 @@ class JobProcessor(_actors.Actor):
|
|
458
438
|
|
459
439
|
def __init__(
|
460
440
|
self, sys_config: _cfg.RuntimeConfig,
|
461
|
-
models: _models.ModelLoader, storage: _storage.StorageManager,
|
462
|
-
job_key: str, job_config: tp.Optional[_cfg.JobConfig], graph_spec: tp.Optional[_graph.Graph]
|
441
|
+
models: _models.ModelLoader, storage: _storage.StorageManager,
|
442
|
+
job_key: str, job_config: tp.Optional[_cfg.JobConfig], graph_spec: tp.Optional[_graph.Graph],
|
443
|
+
job_log: tp.Optional[_JobLog] = None):
|
463
444
|
|
464
445
|
super().__init__()
|
465
446
|
|
@@ -473,9 +454,15 @@ class JobProcessor(_actors.Actor):
|
|
473
454
|
self._sys_config = sys_config
|
474
455
|
self._models = models
|
475
456
|
self._storage = storage
|
476
|
-
|
477
|
-
self.
|
478
|
-
self.
|
457
|
+
|
458
|
+
self._job_log = job_log or _JobLog()
|
459
|
+
self._log_provider = self._job_log.log_provider
|
460
|
+
self._log = self._job_log.log_provider.logger_for_object(self)
|
461
|
+
|
462
|
+
self._log.info(f"New job created for [{self.job_key}]")
|
463
|
+
|
464
|
+
self._resolver = _func.FunctionResolver(models, storage, self._log_provider)
|
465
|
+
self._preallocated_ids: tp.Dict[_meta.ObjectType, tp.List[_meta.TagHeader]] = dict()
|
479
466
|
|
480
467
|
def on_start(self):
|
481
468
|
|
@@ -513,7 +500,10 @@ class JobProcessor(_actors.Actor):
|
|
513
500
|
return super().on_signal(signal)
|
514
501
|
|
515
502
|
@_actors.Message
|
516
|
-
def build_graph_succeeded(self, graph_spec: _graph.Graph):
|
503
|
+
def build_graph_succeeded(self, graph_spec: _graph.Graph, unallocated_ids = None):
|
504
|
+
|
505
|
+
# Save any unallocated IDs to use later (needed for saving the log file)
|
506
|
+
self._preallocated_ids = unallocated_ids or dict()
|
517
507
|
|
518
508
|
# Build a new engine context graph from the graph spec
|
519
509
|
engine_id = self.actors().parent
|
@@ -524,6 +514,7 @@ class JobProcessor(_actors.Actor):
|
|
524
514
|
graph.pending_nodes.update(graph.nodes.keys())
|
525
515
|
|
526
516
|
self.actors().spawn(FunctionResolver(self._resolver, self._log_provider, graph))
|
517
|
+
|
527
518
|
if self.actors().sender != self.actors().id and self.actors().sender != self.actors().parent:
|
528
519
|
self.actors().stop(self.actors().sender)
|
529
520
|
|
@@ -531,20 +522,100 @@ class JobProcessor(_actors.Actor):
|
|
531
522
|
def resolve_functions_succeeded(self, graph: _EngineContext):
|
532
523
|
|
533
524
|
self.actors().spawn(GraphProcessor(graph, self._resolver, self._log_provider))
|
525
|
+
|
534
526
|
if self.actors().sender != self.actors().id and self.actors().sender != self.actors().parent:
|
535
527
|
self.actors().stop(self.actors().sender)
|
536
528
|
|
537
529
|
@_actors.Message
|
538
530
|
def job_succeeded(self, job_result: _cfg.JobResult):
|
539
|
-
|
531
|
+
|
532
|
+
# This will be the last message in the job log file
|
533
|
+
self._log.info(f"Job succeeded [{self.job_key}]")
|
534
|
+
|
535
|
+
self._save_job_log_file(job_result)
|
536
|
+
|
540
537
|
self.actors().stop(self.actors().sender)
|
541
538
|
self.actors().send_parent("job_succeeded", self.job_key, job_result)
|
542
539
|
|
543
540
|
@_actors.Message
|
544
541
|
def job_failed(self, error: Exception):
|
545
|
-
|
542
|
+
|
543
|
+
# This will be the last message in the job log file
|
544
|
+
self._log.error(f"Job failed [{self.job_key}]")
|
545
|
+
|
546
546
|
self.actors().stop(self.actors().sender)
|
547
|
-
|
547
|
+
|
548
|
+
# For top level jobs, build a failed job result and save the log file
|
549
|
+
if self.job_config is not None:
|
550
|
+
|
551
|
+
job_id = self.job_config.jobId
|
552
|
+
result_id = self.job_config.resultId
|
553
|
+
result_def = _meta.ResultDefinition()
|
554
|
+
result_def.jobId = _util.selector_for(job_id)
|
555
|
+
result_def.statusCode = _meta.JobStatusCode.FAILED
|
556
|
+
result_def.statusMessage = str(error)
|
557
|
+
job_result = _cfg.JobResult(job_id, result_id, result_def)
|
558
|
+
|
559
|
+
self._save_job_log_file(job_result)
|
560
|
+
|
561
|
+
self.actors().send_parent("job_failed", self.job_key, error, job_result)
|
562
|
+
|
563
|
+
# For child jobs, just send the error response
|
564
|
+
# Result and log file will be handled in the top level job
|
565
|
+
else:
|
566
|
+
self.actors().send_parent("job_failed", self.job_key, error)
|
567
|
+
|
568
|
+
def _save_job_log_file(self, job_result: _cfg.JobResult):
|
569
|
+
|
570
|
+
# Do not save log files for child jobs, or if a log is not available
|
571
|
+
if self._job_log.log_buffer is None:
|
572
|
+
if self._job_log.log_file_needed:
|
573
|
+
self._log.warning(f"Job log not available for [{self.job_key}]")
|
574
|
+
return
|
575
|
+
|
576
|
+
# Saving log files could go into a separate actor
|
577
|
+
|
578
|
+
file_id = self._allocate_id(_meta.ObjectType.FILE)
|
579
|
+
storage_id = self._allocate_id(_meta.ObjectType.STORAGE)
|
580
|
+
|
581
|
+
file_name = "trac_job_log_file"
|
582
|
+
file_type = _meta.FileType("TXT", "text/plain")
|
583
|
+
|
584
|
+
file_spec = _data.build_file_spec(
|
585
|
+
file_id, storage_id,
|
586
|
+
file_name, file_type,
|
587
|
+
self._sys_config.storage)
|
588
|
+
|
589
|
+
file_def = file_spec.definition
|
590
|
+
storage_def = file_spec.storage
|
591
|
+
|
592
|
+
storage_item = storage_def.dataItems[file_def.dataItem].incarnations[0].copies[0]
|
593
|
+
storage = self._storage.get_file_storage(storage_item.storageKey)
|
594
|
+
|
595
|
+
with storage.write_byte_stream(storage_item.storagePath) as stream:
|
596
|
+
stream.write(self._job_log.log_buffer.getbuffer())
|
597
|
+
file_def.size = stream.tell()
|
598
|
+
|
599
|
+
result_def = job_result.result
|
600
|
+
result_def.logFileId = _util.selector_for(file_id)
|
601
|
+
|
602
|
+
file_obj = _meta.ObjectDefinition(objectType=_meta.ObjectType.FILE, file=file_def)
|
603
|
+
storage_obj = _meta.ObjectDefinition(objectType=_meta.ObjectType.STORAGE, storage=storage_def)
|
604
|
+
|
605
|
+
job_result.objectIds.append(file_id)
|
606
|
+
job_result.objectIds.append(storage_id)
|
607
|
+
job_result.objects[_util.object_key(file_id)] = file_obj
|
608
|
+
job_result.objects[_util.object_key(storage_id)] = storage_obj
|
609
|
+
|
610
|
+
def _allocate_id(self, object_type: _meta.ObjectType):
|
611
|
+
|
612
|
+
preallocated_ids = self._preallocated_ids.get(object_type)
|
613
|
+
|
614
|
+
if preallocated_ids:
|
615
|
+
# Preallocated IDs have objectVersion = 0, use a new version to get objectVersion = 1
|
616
|
+
return _util.new_object_version(preallocated_ids.pop())
|
617
|
+
else:
|
618
|
+
return _util.new_object_id(object_type)
|
548
619
|
|
549
620
|
|
550
621
|
class GraphBuilder(_actors.Actor):
|
@@ -570,8 +641,9 @@ class GraphBuilder(_actors.Actor):
|
|
570
641
|
|
571
642
|
graph_builder = _graph.GraphBuilder(self.sys_config, job_config)
|
572
643
|
graph_spec = graph_builder.build_job(job_config.job)
|
644
|
+
unallocated_ids = graph_builder.unallocated_ids()
|
573
645
|
|
574
|
-
self.actors().reply("build_graph_succeeded", graph_spec)
|
646
|
+
self.actors().reply("build_graph_succeeded", graph_spec, unallocated_ids)
|
575
647
|
|
576
648
|
|
577
649
|
class FunctionResolver(_actors.Actor):
|
@@ -704,23 +776,21 @@ class GraphProcessor(_actors.Actor):
|
|
704
776
|
self.check_job_status(do_submit=False)
|
705
777
|
|
706
778
|
@_actors.Message
|
707
|
-
def update_graph(
|
708
|
-
self, requestor_id: NodeId,
|
709
|
-
new_nodes: tp.Dict[NodeId, _graph.Node],
|
710
|
-
new_deps: tp.Dict[NodeId, tp.List[_graph.Dependency]]):
|
779
|
+
def update_graph(self, requestor_id: NodeId, update: _graph.GraphUpdate):
|
711
780
|
|
712
781
|
new_graph = cp.copy(self.graph)
|
713
782
|
new_graph.nodes = cp.copy(new_graph.nodes)
|
714
783
|
|
715
784
|
# Attempt to insert a duplicate node is always an error
|
716
|
-
node_collision = list(filter(lambda nid: nid in self.graph.nodes,
|
785
|
+
node_collision = list(filter(lambda nid: nid in self.graph.nodes, update.nodes))
|
717
786
|
|
718
787
|
# Only allow adding deps to pending nodes for now (adding deps to active nodes will require more work)
|
719
|
-
dep_collision = list(filter(lambda nid: nid not in self.graph.pending_nodes,
|
788
|
+
dep_collision = list(filter(lambda nid: nid not in self.graph.pending_nodes, update.dependencies))
|
720
789
|
|
790
|
+
# Only allow adding deps to new nodes (deps to existing nodes should not be part of an update)
|
721
791
|
dep_invalid = list(filter(
|
722
|
-
lambda
|
723
|
-
|
792
|
+
lambda ds: any(filter(lambda d: d.node_id not in update.nodes, ds)),
|
793
|
+
update.dependencies.values()))
|
724
794
|
|
725
795
|
if any(node_collision) or any(dep_collision) or any(dep_invalid):
|
726
796
|
|
@@ -736,18 +806,20 @@ class GraphProcessor(_actors.Actor):
|
|
736
806
|
requestor.error = _ex.ETracInternal("Node collision during graph update")
|
737
807
|
new_graph.nodes[requestor_id] = requestor
|
738
808
|
|
809
|
+
self.graph = new_graph
|
810
|
+
|
739
811
|
return
|
740
812
|
|
741
813
|
new_graph.pending_nodes = cp.copy(new_graph.pending_nodes)
|
742
814
|
|
743
|
-
for node_id, node in
|
815
|
+
for node_id, node in update.nodes.items():
|
744
816
|
self._graph_logger.log_node_add(node)
|
745
817
|
node_func = self._resolver.resolve_node(node)
|
746
818
|
new_node = _EngineNode(node, node_func)
|
747
819
|
new_graph.nodes[node_id] = new_node
|
748
820
|
new_graph.pending_nodes.add(node_id)
|
749
821
|
|
750
|
-
for node_id, deps in
|
822
|
+
for node_id, deps in update.dependencies.items():
|
751
823
|
engine_node = cp.copy(new_graph.nodes[node_id])
|
752
824
|
engine_node.dependencies = cp.copy(engine_node.dependencies)
|
753
825
|
for dep in deps:
|
@@ -1302,8 +1374,5 @@ class NodeCallbackImpl(_func.NodeCallback):
|
|
1302
1374
|
self.__actor_ctx = actor_ctx
|
1303
1375
|
self.__node_id = node_id
|
1304
1376
|
|
1305
|
-
def
|
1306
|
-
|
1307
|
-
new_deps: tp.Dict[NodeId, tp.List[_graph.Dependency]]):
|
1308
|
-
|
1309
|
-
self.__actor_ctx.send_parent("update_graph", self.__node_id, new_nodes, new_deps)
|
1377
|
+
def send_graph_update(self, update: _graph.GraphUpdate):
|
1378
|
+
self.__actor_ctx.send_parent("update_graph", self.__node_id, update)
|