prefect-client 3.0.0rc19__py3-none-any.whl → 3.0.0rc20__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.
- prefect/__init__.py +0 -3
- prefect/blocks/core.py +5 -1
- prefect/blocks/system.py +48 -12
- prefect/client/cloud.py +56 -7
- prefect/client/collections.py +1 -1
- prefect/client/orchestration.py +14 -6
- prefect/client/schemas/objects.py +40 -2
- prefect/concurrency/asyncio.py +8 -2
- prefect/concurrency/services.py +16 -6
- prefect/concurrency/sync.py +4 -1
- prefect/exceptions.py +6 -0
- prefect/flow_engine.py +3 -0
- prefect/flows.py +2 -2
- prefect/logging/handlers.py +4 -1
- prefect/main.py +8 -6
- prefect/results.py +222 -170
- prefect/settings.py +14 -7
- prefect/states.py +73 -18
- prefect/task_engine.py +29 -12
- prefect/transactions.py +22 -8
- prefect/utilities/annotations.py +4 -3
- prefect/utilities/asyncutils.py +1 -1
- prefect/utilities/dispatch.py +16 -11
- prefect/utilities/schema_tools/hydration.py +13 -0
- prefect/workers/base.py +78 -18
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.0rc20.dist-info}/METADATA +1 -1
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.0rc20.dist-info}/RECORD +30 -31
- prefect/manifests.py +0 -21
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.0rc20.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.0rc20.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.0rc19.dist-info → prefect_client-3.0.0rc20.dist-info}/top_level.txt +0 -0
prefect/states.py
CHANGED
@@ -18,12 +18,13 @@ from prefect.exceptions import (
|
|
18
18
|
CancelledRun,
|
19
19
|
CrashedRun,
|
20
20
|
FailedRun,
|
21
|
+
MissingContextError,
|
21
22
|
MissingResult,
|
22
23
|
PausedRun,
|
23
24
|
TerminationSignal,
|
24
25
|
UnfinishedRun,
|
25
26
|
)
|
26
|
-
from prefect.logging.loggers import get_logger
|
27
|
+
from prefect.logging.loggers import get_logger, get_run_logger
|
27
28
|
from prefect.results import BaseResult, R, ResultFactory
|
28
29
|
from prefect.settings import PREFECT_ASYNC_FETCH_STATE_RESULT
|
29
30
|
from prefect.utilities.annotations import BaseAnnotation
|
@@ -218,11 +219,17 @@ async def exception_to_crashed_state(
|
|
218
219
|
async def exception_to_failed_state(
|
219
220
|
exc: Optional[BaseException] = None,
|
220
221
|
result_factory: Optional[ResultFactory] = None,
|
222
|
+
write_result: bool = False,
|
221
223
|
**kwargs,
|
222
224
|
) -> State:
|
223
225
|
"""
|
224
226
|
Convenience function for creating `Failed` states from exceptions
|
225
227
|
"""
|
228
|
+
try:
|
229
|
+
local_logger = get_run_logger()
|
230
|
+
except MissingContextError:
|
231
|
+
local_logger = logger
|
232
|
+
|
226
233
|
if not exc:
|
227
234
|
_, exc, _ = sys.exc_info()
|
228
235
|
if exc is None:
|
@@ -234,6 +241,14 @@ async def exception_to_failed_state(
|
|
234
241
|
|
235
242
|
if result_factory:
|
236
243
|
data = await result_factory.create_result(exc)
|
244
|
+
if write_result:
|
245
|
+
try:
|
246
|
+
await data.write()
|
247
|
+
except Exception as exc:
|
248
|
+
local_logger.warning(
|
249
|
+
"Failed to write result: %s Execution will continue, but the result has not been written",
|
250
|
+
exc,
|
251
|
+
)
|
237
252
|
else:
|
238
253
|
# Attach the exception for local usage, will not be available when retrieved
|
239
254
|
# from the API
|
@@ -258,7 +273,7 @@ async def return_value_to_state(
|
|
258
273
|
result_factory: ResultFactory,
|
259
274
|
key: Optional[str] = None,
|
260
275
|
expiration: Optional[datetime.datetime] = None,
|
261
|
-
|
276
|
+
write_result: bool = False,
|
262
277
|
) -> State[R]:
|
263
278
|
"""
|
264
279
|
Given a return value from a user's function, create a `State` the run should
|
@@ -280,6 +295,10 @@ async def return_value_to_state(
|
|
280
295
|
Callers should resolve all futures into states before passing return values to this
|
281
296
|
function.
|
282
297
|
"""
|
298
|
+
try:
|
299
|
+
local_logger = get_run_logger()
|
300
|
+
except MissingContextError:
|
301
|
+
local_logger = logger
|
283
302
|
|
284
303
|
if (
|
285
304
|
isinstance(retval, State)
|
@@ -291,13 +310,20 @@ async def return_value_to_state(
|
|
291
310
|
# Unless the user has already constructed a result explicitly, use the factory
|
292
311
|
# to update the data to the correct type
|
293
312
|
if not isinstance(state.data, BaseResult):
|
294
|
-
|
313
|
+
result = await result_factory.create_result(
|
295
314
|
state.data,
|
296
315
|
key=key,
|
297
316
|
expiration=expiration,
|
298
|
-
defer_persistence=defer_persistence,
|
299
317
|
)
|
300
|
-
|
318
|
+
if write_result:
|
319
|
+
try:
|
320
|
+
await result.write()
|
321
|
+
except Exception as exc:
|
322
|
+
local_logger.warning(
|
323
|
+
"Encountered an error while persisting result: %s Execution will continue, but the result has not been persisted",
|
324
|
+
exc,
|
325
|
+
)
|
326
|
+
state.data = result
|
301
327
|
return state
|
302
328
|
|
303
329
|
# Determine a new state from the aggregate of contained states
|
@@ -333,15 +359,23 @@ async def return_value_to_state(
|
|
333
359
|
# TODO: We may actually want to set the data to a `StateGroup` object and just
|
334
360
|
# allow it to be unpacked into a tuple and such so users can interact with
|
335
361
|
# it
|
362
|
+
result = await result_factory.create_result(
|
363
|
+
retval,
|
364
|
+
key=key,
|
365
|
+
expiration=expiration,
|
366
|
+
)
|
367
|
+
if write_result:
|
368
|
+
try:
|
369
|
+
await result.write()
|
370
|
+
except Exception as exc:
|
371
|
+
local_logger.warning(
|
372
|
+
"Encountered an error while persisting result: %s Execution will continue, but the result has not been persisted",
|
373
|
+
exc,
|
374
|
+
)
|
336
375
|
return State(
|
337
376
|
type=new_state_type,
|
338
377
|
message=message,
|
339
|
-
data=
|
340
|
-
retval,
|
341
|
-
key=key,
|
342
|
-
expiration=expiration,
|
343
|
-
defer_persistence=defer_persistence,
|
344
|
-
),
|
378
|
+
data=result,
|
345
379
|
)
|
346
380
|
|
347
381
|
# Generators aren't portable, implicitly convert them to a list.
|
@@ -354,14 +388,20 @@ async def return_value_to_state(
|
|
354
388
|
if isinstance(data, BaseResult):
|
355
389
|
return Completed(data=data)
|
356
390
|
else:
|
357
|
-
|
358
|
-
data
|
359
|
-
|
360
|
-
|
361
|
-
expiration=expiration,
|
362
|
-
defer_persistence=defer_persistence,
|
363
|
-
)
|
391
|
+
result = await result_factory.create_result(
|
392
|
+
data,
|
393
|
+
key=key,
|
394
|
+
expiration=expiration,
|
364
395
|
)
|
396
|
+
if write_result:
|
397
|
+
try:
|
398
|
+
await result.write()
|
399
|
+
except Exception as exc:
|
400
|
+
local_logger.warning(
|
401
|
+
"Encountered an error while persisting result: %s Execution will continue, but the result has not been persisted",
|
402
|
+
exc,
|
403
|
+
)
|
404
|
+
return Completed(data=result)
|
365
405
|
|
366
406
|
|
367
407
|
@sync_compatible
|
@@ -684,6 +724,21 @@ def AwaitingRetry(
|
|
684
724
|
)
|
685
725
|
|
686
726
|
|
727
|
+
def AwaitingConcurrencySlot(
|
728
|
+
cls: Type[State[R]] = State,
|
729
|
+
scheduled_time: Optional[datetime.datetime] = None,
|
730
|
+
**kwargs: Any,
|
731
|
+
) -> State[R]:
|
732
|
+
"""Convenience function for creating `AwaitingConcurrencySlot` states.
|
733
|
+
|
734
|
+
Returns:
|
735
|
+
State: a AwaitingConcurrencySlot state
|
736
|
+
"""
|
737
|
+
return Scheduled(
|
738
|
+
cls=cls, scheduled_time=scheduled_time, name="AwaitingConcurrencySlot", **kwargs
|
739
|
+
)
|
740
|
+
|
741
|
+
|
687
742
|
def Retrying(cls: Type[State[R]] = State, **kwargs: Any) -> State[R]:
|
688
743
|
"""Convenience function for creating `Retrying` states.
|
689
744
|
|
prefect/task_engine.py
CHANGED
@@ -5,6 +5,7 @@ import time
|
|
5
5
|
from asyncio import CancelledError
|
6
6
|
from contextlib import ExitStack, asynccontextmanager, contextmanager
|
7
7
|
from dataclasses import dataclass, field
|
8
|
+
from functools import partial
|
8
9
|
from textwrap import dedent
|
9
10
|
from typing import (
|
10
11
|
Any,
|
@@ -464,13 +465,16 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
464
465
|
result_factory=result_factory,
|
465
466
|
key=transaction.key,
|
466
467
|
expiration=expiration,
|
467
|
-
# defer persistence to transaction commit
|
468
|
-
defer_persistence=True,
|
469
468
|
)
|
470
469
|
)
|
470
|
+
|
471
|
+
# Avoid logging when running this rollback hook since it is not user-defined
|
472
|
+
handle_rollback = partial(self.handle_rollback)
|
473
|
+
handle_rollback.log_on_run = False
|
474
|
+
|
471
475
|
transaction.stage(
|
472
476
|
terminal_state.data,
|
473
|
-
on_rollback_hooks=[
|
477
|
+
on_rollback_hooks=[handle_rollback] + self.task.on_rollback_hooks,
|
474
478
|
on_commit_hooks=self.task.on_commit_hooks,
|
475
479
|
)
|
476
480
|
if transaction.is_committed():
|
@@ -536,6 +540,7 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
536
540
|
exc,
|
537
541
|
message="Task run encountered an exception",
|
538
542
|
result_factory=getattr(context, "result_factory", None),
|
543
|
+
write_result=True,
|
539
544
|
)
|
540
545
|
)
|
541
546
|
self.record_terminal_state_timing(state)
|
@@ -705,17 +710,22 @@ class SyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
705
710
|
|
706
711
|
@contextmanager
|
707
712
|
def transaction_context(self) -> Generator[Transaction, None, None]:
|
708
|
-
result_factory = getattr(TaskRunContext.get(), "result_factory", None)
|
709
|
-
|
710
713
|
# refresh cache setting is now repurposes as overwrite transaction record
|
711
714
|
overwrite = (
|
712
715
|
self.task.refresh_cache
|
713
716
|
if self.task.refresh_cache is not None
|
714
717
|
else PREFECT_TASKS_REFRESH_CACHE.value()
|
715
718
|
)
|
719
|
+
|
720
|
+
result_factory = getattr(TaskRunContext.get(), "result_factory", None)
|
721
|
+
if result_factory and result_factory.persist_result:
|
722
|
+
store = ResultFactoryStore(result_factory=result_factory)
|
723
|
+
else:
|
724
|
+
store = None
|
725
|
+
|
716
726
|
with transaction(
|
717
727
|
key=self.compute_transaction_key(),
|
718
|
-
store=
|
728
|
+
store=store,
|
719
729
|
overwrite=overwrite,
|
720
730
|
logger=self.logger,
|
721
731
|
) as txn:
|
@@ -964,12 +974,15 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
964
974
|
result_factory=result_factory,
|
965
975
|
key=transaction.key,
|
966
976
|
expiration=expiration,
|
967
|
-
# defer persistence to transaction commit
|
968
|
-
defer_persistence=True,
|
969
977
|
)
|
978
|
+
|
979
|
+
# Avoid logging when running this rollback hook since it is not user-defined
|
980
|
+
handle_rollback = partial(self.handle_rollback)
|
981
|
+
handle_rollback.log_on_run = False
|
982
|
+
|
970
983
|
transaction.stage(
|
971
984
|
terminal_state.data,
|
972
|
-
on_rollback_hooks=[
|
985
|
+
on_rollback_hooks=[handle_rollback] + self.task.on_rollback_hooks,
|
973
986
|
on_commit_hooks=self.task.on_commit_hooks,
|
974
987
|
)
|
975
988
|
if transaction.is_committed():
|
@@ -1199,17 +1212,21 @@ class AsyncTaskRunEngine(BaseTaskRunEngine[P, R]):
|
|
1199
1212
|
|
1200
1213
|
@asynccontextmanager
|
1201
1214
|
async def transaction_context(self) -> AsyncGenerator[Transaction, None]:
|
1202
|
-
result_factory = getattr(TaskRunContext.get(), "result_factory", None)
|
1203
|
-
|
1204
1215
|
# refresh cache setting is now repurposes as overwrite transaction record
|
1205
1216
|
overwrite = (
|
1206
1217
|
self.task.refresh_cache
|
1207
1218
|
if self.task.refresh_cache is not None
|
1208
1219
|
else PREFECT_TASKS_REFRESH_CACHE.value()
|
1209
1220
|
)
|
1221
|
+
result_factory = getattr(TaskRunContext.get(), "result_factory", None)
|
1222
|
+
if result_factory and result_factory.persist_result:
|
1223
|
+
store = ResultFactoryStore(result_factory=result_factory)
|
1224
|
+
else:
|
1225
|
+
store = None
|
1226
|
+
|
1210
1227
|
with transaction(
|
1211
1228
|
key=self.compute_transaction_key(),
|
1212
|
-
store=
|
1229
|
+
store=store,
|
1213
1230
|
overwrite=overwrite,
|
1214
1231
|
logger=self.logger,
|
1215
1232
|
) as txn:
|
prefect/transactions.py
CHANGED
@@ -18,7 +18,7 @@ from pydantic import Field, PrivateAttr
|
|
18
18
|
from typing_extensions import Self
|
19
19
|
|
20
20
|
from prefect.context import ContextModel, FlowRunContext, TaskRunContext
|
21
|
-
from prefect.exceptions import MissingContextError
|
21
|
+
from prefect.exceptions import MissingContextError, SerializationError
|
22
22
|
from prefect.logging.loggers import get_logger, get_run_logger
|
23
23
|
from prefect.records import RecordStore
|
24
24
|
from prefect.results import (
|
@@ -240,6 +240,14 @@ class Transaction(ContextModel):
|
|
240
240
|
self.logger.debug(f"Releasing lock for transaction {self.key!r}")
|
241
241
|
self.store.release_lock(self.key)
|
242
242
|
return True
|
243
|
+
except SerializationError as exc:
|
244
|
+
if self.logger:
|
245
|
+
self.logger.warning(
|
246
|
+
f"Encountered an error while serializing result for transaction {self.key!r}: {exc}"
|
247
|
+
" Code execution will continue, but the transaction will not be committed.",
|
248
|
+
)
|
249
|
+
self.rollback()
|
250
|
+
return False
|
243
251
|
except Exception:
|
244
252
|
if self.logger:
|
245
253
|
self.logger.exception(
|
@@ -251,19 +259,25 @@ class Transaction(ContextModel):
|
|
251
259
|
|
252
260
|
def run_hook(self, hook, hook_type: str) -> None:
|
253
261
|
hook_name = _get_hook_name(hook)
|
254
|
-
|
262
|
+
# Undocumented way to disable logging for a hook. Subject to change.
|
263
|
+
should_log = getattr(hook, "log_on_run", True)
|
264
|
+
|
265
|
+
if should_log:
|
266
|
+
self.logger.info(f"Running {hook_type} hook {hook_name!r}")
|
255
267
|
|
256
268
|
try:
|
257
269
|
hook(self)
|
258
270
|
except Exception as exc:
|
259
|
-
|
260
|
-
|
261
|
-
|
271
|
+
if should_log:
|
272
|
+
self.logger.error(
|
273
|
+
f"An error was encountered while running {hook_type} hook {hook_name!r}",
|
274
|
+
)
|
262
275
|
raise exc
|
263
276
|
else:
|
264
|
-
|
265
|
-
|
266
|
-
|
277
|
+
if should_log:
|
278
|
+
self.logger.info(
|
279
|
+
f"{hook_type.capitalize()} hook {hook_name!r} finished running successfully"
|
280
|
+
)
|
267
281
|
|
268
282
|
def stage(
|
269
283
|
self,
|
prefect/utilities/annotations.py
CHANGED
@@ -21,8 +21,8 @@ class BaseAnnotation(
|
|
21
21
|
def rewrap(self, value: T) -> "BaseAnnotation[T]":
|
22
22
|
return type(self)(value)
|
23
23
|
|
24
|
-
def __eq__(self, other:
|
25
|
-
if
|
24
|
+
def __eq__(self, other: "BaseAnnotation[T]") -> bool:
|
25
|
+
if type(self) is not type(other):
|
26
26
|
return False
|
27
27
|
return self.unwrap() == other.unwrap()
|
28
28
|
|
@@ -90,10 +90,11 @@ class quote(BaseAnnotation[T]):
|
|
90
90
|
class Quote(quote):
|
91
91
|
def __init__(self, expr):
|
92
92
|
warnings.warn(
|
93
|
-
DeprecationWarning,
|
94
93
|
"Use of `Quote` is deprecated. Use `quote` instead.",
|
94
|
+
DeprecationWarning,
|
95
95
|
stacklevel=2,
|
96
96
|
)
|
97
|
+
super().__init__(expr)
|
97
98
|
|
98
99
|
|
99
100
|
class NotSet:
|
prefect/utilities/asyncutils.py
CHANGED
prefect/utilities/dispatch.py
CHANGED
@@ -162,17 +162,22 @@ def register_type(cls: T) -> T:
|
|
162
162
|
key = get_dispatch_key(cls)
|
163
163
|
existing_value = registry.get(key)
|
164
164
|
if existing_value is not None and id(existing_value) != id(cls):
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
165
|
+
try:
|
166
|
+
# Get line numbers for debugging
|
167
|
+
file = inspect.getsourcefile(cls)
|
168
|
+
line_number = inspect.getsourcelines(cls)[1]
|
169
|
+
existing_file = inspect.getsourcefile(existing_value)
|
170
|
+
existing_line_number = inspect.getsourcelines(existing_value)[1]
|
171
|
+
warnings.warn(
|
172
|
+
f"Type {cls.__name__!r} at {file}:{line_number} has key {key!r} that "
|
173
|
+
f"matches existing registered type {existing_value.__name__!r} from "
|
174
|
+
f"{existing_file}:{existing_line_number}. The existing type will be "
|
175
|
+
"overridden."
|
176
|
+
)
|
177
|
+
except OSError:
|
178
|
+
# If we can't get the source, another actor is loading this class via eval
|
179
|
+
# and we shouldn't update the registry
|
180
|
+
return cls
|
176
181
|
|
177
182
|
# Add to the registry
|
178
183
|
registry[key] = cls
|
@@ -202,6 +202,11 @@ def json_handler(obj: dict, ctx: HydrationContext):
|
|
202
202
|
dehydrated_json = _hydrate(obj["value"], ctx)
|
203
203
|
else:
|
204
204
|
dehydrated_json = obj["value"]
|
205
|
+
|
206
|
+
# If the result is a Placeholder, we should return it as is
|
207
|
+
if isinstance(dehydrated_json, Placeholder):
|
208
|
+
return dehydrated_json
|
209
|
+
|
205
210
|
try:
|
206
211
|
return json.loads(dehydrated_json)
|
207
212
|
except (json.decoder.JSONDecodeError, TypeError) as e:
|
@@ -224,6 +229,10 @@ def jinja_handler(obj: dict, ctx: HydrationContext):
|
|
224
229
|
else:
|
225
230
|
dehydrated_jinja = obj["template"]
|
226
231
|
|
232
|
+
# If the result is a Placeholder, we should return it as is
|
233
|
+
if isinstance(dehydrated_jinja, Placeholder):
|
234
|
+
return dehydrated_jinja
|
235
|
+
|
227
236
|
try:
|
228
237
|
validate_user_template(dehydrated_jinja)
|
229
238
|
except (jinja2.exceptions.TemplateSyntaxError, TemplateSecurityError) as exc:
|
@@ -245,6 +254,10 @@ def workspace_variable_handler(obj: dict, ctx: HydrationContext):
|
|
245
254
|
else:
|
246
255
|
dehydrated_variable = obj["variable_name"]
|
247
256
|
|
257
|
+
# If the result is a Placeholder, we should return it as is
|
258
|
+
if isinstance(dehydrated_variable, Placeholder):
|
259
|
+
return dehydrated_variable
|
260
|
+
|
248
261
|
if not ctx.render_workspace_variables:
|
249
262
|
return WorkspaceVariable(variable_name=obj["variable_name"])
|
250
263
|
|
prefect/workers/base.py
CHANGED
@@ -19,6 +19,11 @@ from prefect.client.orchestration import PrefectClient, get_client
|
|
19
19
|
from prefect.client.schemas.actions import WorkPoolCreate, WorkPoolUpdate
|
20
20
|
from prefect.client.schemas.objects import StateType, WorkPool
|
21
21
|
from prefect.client.utilities import inject_client
|
22
|
+
from prefect.concurrency.asyncio import (
|
23
|
+
AcquireConcurrencySlotTimeoutError,
|
24
|
+
ConcurrencySlotAcquisitionError,
|
25
|
+
concurrency,
|
26
|
+
)
|
22
27
|
from prefect.events import Event, RelatedResource, emit_event
|
23
28
|
from prefect.events.related import object_as_related_resource, tags_as_related_resources
|
24
29
|
from prefect.exceptions import (
|
@@ -35,7 +40,13 @@ from prefect.settings import (
|
|
35
40
|
PREFECT_WORKER_QUERY_SECONDS,
|
36
41
|
get_current_settings,
|
37
42
|
)
|
38
|
-
from prefect.states import
|
43
|
+
from prefect.states import (
|
44
|
+
AwaitingConcurrencySlot,
|
45
|
+
Crashed,
|
46
|
+
Pending,
|
47
|
+
exception_to_failed_state,
|
48
|
+
)
|
49
|
+
from prefect.utilities.asyncutils import asyncnullcontext
|
39
50
|
from prefect.utilities.dispatch import get_registry_for_type, register_base_type
|
40
51
|
from prefect.utilities.engine import propose_state
|
41
52
|
from prefect.utilities.services import critical_service_loop
|
@@ -654,6 +665,7 @@ class BaseWorker(abc.ABC):
|
|
654
665
|
work_pool = await self._client.read_work_pool(
|
655
666
|
work_pool_name=self._work_pool_name
|
656
667
|
)
|
668
|
+
|
657
669
|
except ObjectNotFound:
|
658
670
|
if self._create_pool_if_not_found:
|
659
671
|
wp = WorkPoolCreate(
|
@@ -747,11 +759,10 @@ class BaseWorker(abc.ABC):
|
|
747
759
|
for execution by the worker.
|
748
760
|
"""
|
749
761
|
submittable_flow_runs = [entry.flow_run for entry in flow_run_response]
|
750
|
-
|
762
|
+
|
751
763
|
for flow_run in submittable_flow_runs:
|
752
764
|
if flow_run.id in self._submitting_flow_run_ids:
|
753
765
|
continue
|
754
|
-
|
755
766
|
try:
|
756
767
|
if self._limiter:
|
757
768
|
self._limiter.acquire_on_behalf_of_nowait(flow_run.id)
|
@@ -796,8 +807,6 @@ class BaseWorker(abc.ABC):
|
|
796
807
|
" Please use an agent to execute this flow run."
|
797
808
|
)
|
798
809
|
|
799
|
-
#
|
800
|
-
|
801
810
|
async def _submit_run(self, flow_run: "FlowRun") -> None:
|
802
811
|
"""
|
803
812
|
Submits a given flow run for execution by the worker.
|
@@ -837,28 +846,59 @@ class BaseWorker(abc.ABC):
|
|
837
846
|
"not be cancellable."
|
838
847
|
)
|
839
848
|
|
840
|
-
|
849
|
+
run_logger.info(f"Completed submission of flow run '{flow_run.id}'")
|
841
850
|
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
851
|
+
else:
|
852
|
+
# If the run is not ready to submit, release the concurrency slot
|
853
|
+
if self._limiter:
|
854
|
+
self._limiter.release_on_behalf_of(flow_run.id)
|
846
855
|
|
847
|
-
|
856
|
+
self._submitting_flow_run_ids.remove(flow_run.id)
|
848
857
|
|
849
858
|
async def _submit_run_and_capture_errors(
|
850
859
|
self, flow_run: "FlowRun", task_status: Optional[anyio.abc.TaskStatus] = None
|
851
860
|
) -> Union[BaseWorkerResult, Exception]:
|
852
861
|
run_logger = self.get_flow_run_logger(flow_run)
|
862
|
+
deployment = None
|
863
|
+
|
864
|
+
if flow_run.deployment_id:
|
865
|
+
deployment = await self._client.read_deployment(flow_run.deployment_id)
|
866
|
+
if deployment and deployment.concurrency_limit:
|
867
|
+
limit_name = f"deployment:{deployment.id}"
|
868
|
+
concurrency_limit = deployment.concurrency_limit
|
869
|
+
concurrency_ctx = concurrency
|
870
|
+
else:
|
871
|
+
limit_name = None
|
872
|
+
concurrency_limit = None
|
873
|
+
concurrency_ctx = asyncnullcontext
|
853
874
|
|
854
875
|
try:
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
876
|
+
async with concurrency_ctx(
|
877
|
+
limit_name, occupy=concurrency_limit, max_retries=0
|
878
|
+
):
|
879
|
+
configuration = await self._get_configuration(flow_run, deployment)
|
880
|
+
submitted_event = self._emit_flow_run_submitted_event(configuration)
|
881
|
+
result = await self.run(
|
882
|
+
flow_run=flow_run,
|
883
|
+
task_status=task_status,
|
884
|
+
configuration=configuration,
|
885
|
+
)
|
886
|
+
except (
|
887
|
+
AcquireConcurrencySlotTimeoutError,
|
888
|
+
ConcurrencySlotAcquisitionError,
|
889
|
+
) as exc:
|
890
|
+
self._logger.info(
|
891
|
+
(
|
892
|
+
"Deployment %s has reached its concurrency limit when submitting flow run %s"
|
893
|
+
),
|
894
|
+
flow_run.deployment_id,
|
895
|
+
flow_run.name,
|
861
896
|
)
|
897
|
+
await self._propose_scheduled_state(flow_run)
|
898
|
+
|
899
|
+
if not task_status._future.done():
|
900
|
+
task_status.started(exc)
|
901
|
+
return exc
|
862
902
|
except Exception as exc:
|
863
903
|
if not task_status._future.done():
|
864
904
|
# This flow run was being submitted and did not start successfully
|
@@ -924,8 +964,13 @@ class BaseWorker(abc.ABC):
|
|
924
964
|
async def _get_configuration(
|
925
965
|
self,
|
926
966
|
flow_run: "FlowRun",
|
967
|
+
deployment: Optional["DeploymentResponse"] = None,
|
927
968
|
) -> BaseJobConfiguration:
|
928
|
-
deployment =
|
969
|
+
deployment = (
|
970
|
+
deployment
|
971
|
+
if deployment
|
972
|
+
else await self._client.read_deployment(flow_run.deployment_id)
|
973
|
+
)
|
929
974
|
flow = await self._client.read_flow(flow_run.flow_id)
|
930
975
|
|
931
976
|
deployment_vars = deployment.job_variables or {}
|
@@ -979,6 +1024,21 @@ class BaseWorker(abc.ABC):
|
|
979
1024
|
|
980
1025
|
return True
|
981
1026
|
|
1027
|
+
async def _propose_scheduled_state(self, flow_run: "FlowRun") -> None:
|
1028
|
+
run_logger = self.get_flow_run_logger(flow_run)
|
1029
|
+
try:
|
1030
|
+
state = await propose_state(
|
1031
|
+
self._client,
|
1032
|
+
AwaitingConcurrencySlot(),
|
1033
|
+
flow_run_id=flow_run.id,
|
1034
|
+
)
|
1035
|
+
self._logger.info(f"Flow run {flow_run.id} now has state {state.name}")
|
1036
|
+
except Abort:
|
1037
|
+
# Flow run already marked as failed
|
1038
|
+
pass
|
1039
|
+
except Exception:
|
1040
|
+
run_logger.exception(f"Failed to update state of flow run '{flow_run.id}'")
|
1041
|
+
|
982
1042
|
async def _propose_failed_state(self, flow_run: "FlowRun", exc: Exception) -> None:
|
983
1043
|
run_logger = self.get_flow_run_logger(flow_run)
|
984
1044
|
try:
|