durabletask 1.3.0.dev28__tar.gz → 1.3.0.dev29__tar.gz
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.
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/PKG-INFO +1 -1
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/task.py +67 -6
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/worker.py +105 -31
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask.egg-info/PKG-INFO +1 -1
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/pyproject.toml +1 -1
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/LICENSE +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/README.md +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/__init__.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/client.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/entities/__init__.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/entities/durable_entity.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/entities/entity_context.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/entities/entity_instance_id.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/entities/entity_lock.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/entities/entity_metadata.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/entities/entity_operation_failed_exception.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/extensions/__init__.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/extensions/azure_blob_payloads/__init__.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/extensions/azure_blob_payloads/blob_payload_store.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/extensions/azure_blob_payloads/options.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/client_helpers.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/entity_state_shim.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/exceptions.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/grpc_interceptor.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/helpers.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/json_encode_output_exception.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/orchestration_entity_context.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/orchestrator_service_pb2.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/orchestrator_service_pb2.pyi +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/orchestrator_service_pb2_grpc.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/proto_task_hub_sidecar_service_stub.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/shared.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/tracing.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/payload/__init__.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/payload/helpers.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/payload/store.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/py.typed +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/testing/__init__.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/testing/in_memory_backend.py +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask.egg-info/SOURCES.txt +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask.egg-info/dependency_links.txt +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask.egg-info/requires.txt +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask.egg-info/top_level.txt +0 -0
- {durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/setup.cfg +0 -0
|
@@ -98,7 +98,7 @@ class OrchestrationContext(ABC):
|
|
|
98
98
|
pass
|
|
99
99
|
|
|
100
100
|
@abstractmethod
|
|
101
|
-
def create_timer(self, fire_at: Union[datetime, timedelta]) ->
|
|
101
|
+
def create_timer(self, fire_at: Union[datetime, timedelta]) -> CancellableTask:
|
|
102
102
|
"""Create a Timer Task to fire after at the specified deadline.
|
|
103
103
|
|
|
104
104
|
Parameters
|
|
@@ -228,10 +228,10 @@ class OrchestrationContext(ABC):
|
|
|
228
228
|
"""
|
|
229
229
|
pass
|
|
230
230
|
|
|
231
|
-
# TOOD: Add a timeout parameter, which allows the task to be
|
|
231
|
+
# TOOD: Add a timeout parameter, which allows the task to be cancelled if the event is
|
|
232
232
|
# not received within the specified timeout. This requires support for task cancellation.
|
|
233
233
|
@abstractmethod
|
|
234
|
-
def wait_for_external_event(self, name: str) ->
|
|
234
|
+
def wait_for_external_event(self, name: str) -> CancellableTask:
|
|
235
235
|
"""Wait asynchronously for an event to be raised with the name `name`.
|
|
236
236
|
|
|
237
237
|
Parameters
|
|
@@ -324,6 +324,10 @@ class OrchestrationStateError(Exception):
|
|
|
324
324
|
pass
|
|
325
325
|
|
|
326
326
|
|
|
327
|
+
class TaskCancelledError(Exception):
|
|
328
|
+
"""Exception type for cancelled orchestration tasks."""
|
|
329
|
+
|
|
330
|
+
|
|
327
331
|
class Task(ABC, Generic[T]):
|
|
328
332
|
"""Abstract base class for asynchronous tasks in a durable orchestration."""
|
|
329
333
|
_result: T
|
|
@@ -435,6 +439,48 @@ class CompletableTask(Task[T]):
|
|
|
435
439
|
self._parent.on_child_completed(self)
|
|
436
440
|
|
|
437
441
|
|
|
442
|
+
class CancellableTask(CompletableTask[T]):
|
|
443
|
+
"""A completable task that can be cancelled before it finishes."""
|
|
444
|
+
|
|
445
|
+
def __init__(self) -> None:
|
|
446
|
+
super().__init__()
|
|
447
|
+
self._is_cancelled = False
|
|
448
|
+
self._cancel_handler: Optional[Callable[[], None]] = None
|
|
449
|
+
|
|
450
|
+
@property
|
|
451
|
+
def is_cancelled(self) -> bool:
|
|
452
|
+
"""Returns True if the task was cancelled, False otherwise."""
|
|
453
|
+
return self._is_cancelled
|
|
454
|
+
|
|
455
|
+
def get_result(self) -> T:
|
|
456
|
+
if self._is_cancelled:
|
|
457
|
+
raise TaskCancelledError('The task was cancelled.')
|
|
458
|
+
return super().get_result()
|
|
459
|
+
|
|
460
|
+
def set_cancel_handler(self, cancel_handler: Callable[[], None]) -> None:
|
|
461
|
+
self._cancel_handler = cancel_handler
|
|
462
|
+
|
|
463
|
+
def cancel(self) -> bool:
|
|
464
|
+
"""Attempts to cancel this task.
|
|
465
|
+
|
|
466
|
+
Returns
|
|
467
|
+
-------
|
|
468
|
+
bool
|
|
469
|
+
True if cancellation was applied, False if the task had already completed.
|
|
470
|
+
"""
|
|
471
|
+
if self._is_complete:
|
|
472
|
+
return False
|
|
473
|
+
|
|
474
|
+
if self._cancel_handler is not None:
|
|
475
|
+
self._cancel_handler()
|
|
476
|
+
|
|
477
|
+
self._is_cancelled = True
|
|
478
|
+
self._is_complete = True
|
|
479
|
+
if self._parent is not None:
|
|
480
|
+
self._parent.on_child_completed(self)
|
|
481
|
+
return True
|
|
482
|
+
|
|
483
|
+
|
|
438
484
|
class RetryableTask(CompletableTask[T]):
|
|
439
485
|
"""A task that can be retried according to a retry policy."""
|
|
440
486
|
|
|
@@ -474,14 +520,29 @@ class RetryableTask(CompletableTask[T]):
|
|
|
474
520
|
return None
|
|
475
521
|
|
|
476
522
|
|
|
477
|
-
class TimerTask(
|
|
478
|
-
|
|
479
|
-
|
|
523
|
+
class TimerTask(CancellableTask[None]):
|
|
524
|
+
def __init__(self, final_fire_at: Optional[datetime] = None,
|
|
525
|
+
maximum_timer_interval: Optional[timedelta] = None):
|
|
480
526
|
super().__init__()
|
|
527
|
+
self._final_fire_at = final_fire_at
|
|
528
|
+
self._maximum_timer_interval = maximum_timer_interval
|
|
481
529
|
|
|
482
530
|
def set_retryable_parent(self, retryable_task: RetryableTask):
|
|
483
531
|
self._retryable_parent = retryable_task
|
|
484
532
|
|
|
533
|
+
def _handle_timer_fired(self, current_utc_datetime: datetime) -> Optional[datetime]:
|
|
534
|
+
if (self._final_fire_at is not None
|
|
535
|
+
and self._maximum_timer_interval is not None
|
|
536
|
+
and current_utc_datetime < self._final_fire_at):
|
|
537
|
+
return self._get_next_fire_at(current_utc_datetime)
|
|
538
|
+
super().complete(None)
|
|
539
|
+
return None
|
|
540
|
+
|
|
541
|
+
def _get_next_fire_at(self, current_utc_datetime: datetime) -> datetime:
|
|
542
|
+
if current_utc_datetime + self._maximum_timer_interval < self._final_fire_at:
|
|
543
|
+
return current_utc_datetime + self._maximum_timer_interval
|
|
544
|
+
return self._final_fire_at
|
|
545
|
+
|
|
485
546
|
|
|
486
547
|
class WhenAnyTask(CompositeTask[Task]):
|
|
487
548
|
"""A task that completes when any of its child tasks complete."""
|
|
@@ -43,6 +43,7 @@ from durabletask.payload.store import PayloadStore
|
|
|
43
43
|
TInput = TypeVar("TInput")
|
|
44
44
|
TOutput = TypeVar("TOutput")
|
|
45
45
|
DATETIME_STRING_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
|
|
46
|
+
DEFAULT_MAXIMUM_TIMER_INTERVAL = timedelta(days=3)
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
class ConcurrencyOptions:
|
|
@@ -415,7 +416,7 @@ class TaskHubGrpcWorker:
|
|
|
415
416
|
activity function.
|
|
416
417
|
"""
|
|
417
418
|
|
|
418
|
-
_response_stream: Optional[
|
|
419
|
+
_response_stream: Optional[Any] = None
|
|
419
420
|
_interceptors: Optional[list[shared.ClientInterceptor]] = None
|
|
420
421
|
|
|
421
422
|
def __init__(
|
|
@@ -428,6 +429,7 @@ class TaskHubGrpcWorker:
|
|
|
428
429
|
secure_channel: bool = False,
|
|
429
430
|
interceptors: Optional[Sequence[shared.ClientInterceptor]] = None,
|
|
430
431
|
concurrency_options: Optional[ConcurrencyOptions] = None,
|
|
432
|
+
maximum_timer_interval: Optional[timedelta] = DEFAULT_MAXIMUM_TIMER_INTERVAL,
|
|
431
433
|
payload_store: Optional[PayloadStore] = None,
|
|
432
434
|
):
|
|
433
435
|
self._registry = _Registry()
|
|
@@ -458,6 +460,7 @@ class TaskHubGrpcWorker:
|
|
|
458
460
|
self._interceptors = None
|
|
459
461
|
|
|
460
462
|
self._async_worker_manager = _AsyncWorkerManager(self._concurrency_options, self._logger)
|
|
463
|
+
self._maximum_timer_interval = maximum_timer_interval
|
|
461
464
|
self._work_item_filters: Optional[WorkItemFilters] = None
|
|
462
465
|
self._auto_generate_work_item_filters: bool = False
|
|
463
466
|
|
|
@@ -466,6 +469,11 @@ class TaskHubGrpcWorker:
|
|
|
466
469
|
"""Get the current concurrency options for this worker."""
|
|
467
470
|
return self._concurrency_options
|
|
468
471
|
|
|
472
|
+
@property
|
|
473
|
+
def maximum_timer_interval(self) -> Optional[timedelta]:
|
|
474
|
+
"""Get the configured maximum timer interval for long timer chunking."""
|
|
475
|
+
return self._maximum_timer_interval
|
|
476
|
+
|
|
469
477
|
def __enter__(self):
|
|
470
478
|
return self
|
|
471
479
|
|
|
@@ -686,7 +694,11 @@ class TaskHubGrpcWorker:
|
|
|
686
694
|
|
|
687
695
|
def stream_reader():
|
|
688
696
|
try:
|
|
689
|
-
|
|
697
|
+
response_stream = self._response_stream
|
|
698
|
+
if response_stream is None:
|
|
699
|
+
return
|
|
700
|
+
|
|
701
|
+
for work_item in response_stream:
|
|
690
702
|
work_item_queue.put(work_item)
|
|
691
703
|
except Exception as e:
|
|
692
704
|
work_item_queue.put(e)
|
|
@@ -838,7 +850,8 @@ class TaskHubGrpcWorker:
|
|
|
838
850
|
try:
|
|
839
851
|
executor = _OrchestrationExecutor(
|
|
840
852
|
self._registry, self._logger,
|
|
841
|
-
persisted_orch_span_id=persisted_orch_span_id
|
|
853
|
+
persisted_orch_span_id=persisted_orch_span_id,
|
|
854
|
+
maximum_timer_interval=self.maximum_timer_interval)
|
|
842
855
|
result = executor.execute(instance_id, req.pastEvents, req.newEvents)
|
|
843
856
|
|
|
844
857
|
# Determine completion status for span
|
|
@@ -1126,7 +1139,11 @@ class _RuntimeOrchestrationContext(task.OrchestrationContext):
|
|
|
1126
1139
|
_generator: Optional[Generator[task.Task, Any, Any]]
|
|
1127
1140
|
_previous_task: Optional[task.Task]
|
|
1128
1141
|
|
|
1129
|
-
def __init__(self,
|
|
1142
|
+
def __init__(self,
|
|
1143
|
+
instance_id: str,
|
|
1144
|
+
registry: _Registry,
|
|
1145
|
+
maximum_timer_interval: Optional[timedelta] = DEFAULT_MAXIMUM_TIMER_INTERVAL,
|
|
1146
|
+
):
|
|
1130
1147
|
self._generator = None
|
|
1131
1148
|
self._is_replaying = True
|
|
1132
1149
|
self._is_complete = False
|
|
@@ -1147,12 +1164,13 @@ class _RuntimeOrchestrationContext(task.OrchestrationContext):
|
|
|
1147
1164
|
self._version: Optional[str] = None
|
|
1148
1165
|
self._completion_status: Optional[pb.OrchestrationStatus] = None
|
|
1149
1166
|
self._received_events: dict[str, list[Any]] = {}
|
|
1150
|
-
self._pending_events: dict[str, list[task.
|
|
1167
|
+
self._pending_events: dict[str, list[task.CancellableTask]] = {}
|
|
1151
1168
|
self._new_input: Optional[Any] = None
|
|
1152
1169
|
self._save_events = False
|
|
1153
1170
|
self._encoded_custom_status: Optional[str] = None
|
|
1154
1171
|
self._parent_trace_context: Optional[pb.TraceContext] = None
|
|
1155
1172
|
self._orchestration_trace_context: Optional[pb.TraceContext] = None
|
|
1173
|
+
self._maximum_timer_interval = maximum_timer_interval
|
|
1156
1174
|
|
|
1157
1175
|
def run(self, generator: Generator[task.Task, Any, Any]):
|
|
1158
1176
|
self._generator = generator
|
|
@@ -1318,7 +1336,7 @@ class _RuntimeOrchestrationContext(task.OrchestrationContext):
|
|
|
1318
1336
|
shared.to_json(custom_status) if custom_status is not None else None
|
|
1319
1337
|
)
|
|
1320
1338
|
|
|
1321
|
-
def create_timer(self, fire_at: Union[datetime, timedelta]) -> task.
|
|
1339
|
+
def create_timer(self, fire_at: Union[datetime, timedelta]) -> task.CancellableTask:
|
|
1322
1340
|
return self.create_timer_internal(fire_at)
|
|
1323
1341
|
|
|
1324
1342
|
def create_timer_internal(
|
|
@@ -1328,11 +1346,30 @@ class _RuntimeOrchestrationContext(task.OrchestrationContext):
|
|
|
1328
1346
|
) -> task.TimerTask:
|
|
1329
1347
|
id = self.next_sequence_number()
|
|
1330
1348
|
if isinstance(fire_at, timedelta):
|
|
1331
|
-
|
|
1332
|
-
|
|
1349
|
+
final_fire_at = self.current_utc_datetime + fire_at
|
|
1350
|
+
else:
|
|
1351
|
+
final_fire_at = fire_at
|
|
1352
|
+
|
|
1353
|
+
next_fire_at: datetime = final_fire_at
|
|
1354
|
+
|
|
1355
|
+
if (
|
|
1356
|
+
self._maximum_timer_interval is not None
|
|
1357
|
+
and self._maximum_timer_interval > timedelta(0)
|
|
1358
|
+
and self.current_utc_datetime + self._maximum_timer_interval < final_fire_at
|
|
1359
|
+
):
|
|
1360
|
+
timer_task = task.TimerTask(final_fire_at, self._maximum_timer_interval)
|
|
1361
|
+
next_fire_at = timer_task._get_next_fire_at(self.current_utc_datetime)
|
|
1362
|
+
else:
|
|
1363
|
+
timer_task = task.TimerTask()
|
|
1364
|
+
|
|
1365
|
+
action = ph.new_create_timer_action(id, next_fire_at)
|
|
1333
1366
|
self._pending_actions[id] = action
|
|
1334
1367
|
|
|
1335
|
-
|
|
1368
|
+
def _cancel_timer() -> None:
|
|
1369
|
+
self._pending_actions.pop(id, None)
|
|
1370
|
+
self._pending_tasks.pop(id, None)
|
|
1371
|
+
|
|
1372
|
+
timer_task.set_cancel_handler(_cancel_timer)
|
|
1336
1373
|
if retryable_task is not None:
|
|
1337
1374
|
timer_task.set_retryable_parent(retryable_task)
|
|
1338
1375
|
self._pending_tasks[id] = timer_task
|
|
@@ -1563,13 +1600,13 @@ class _RuntimeOrchestrationContext(task.OrchestrationContext):
|
|
|
1563
1600
|
action = pb.OrchestratorAction(id=task_id, sendEntityMessage=entity_unlock_message)
|
|
1564
1601
|
self._pending_actions[task_id] = action
|
|
1565
1602
|
|
|
1566
|
-
def wait_for_external_event(self, name: str) -> task.
|
|
1603
|
+
def wait_for_external_event(self, name: str) -> task.CancellableTask:
|
|
1567
1604
|
# Check to see if this event has already been received, in which case we
|
|
1568
1605
|
# can return it immediately. Otherwise, record out intent to receive an
|
|
1569
1606
|
# event with the given name so that we can resume the generator when it
|
|
1570
1607
|
# arrives. If there are multiple events with the same name, we return
|
|
1571
1608
|
# them in the order they were received.
|
|
1572
|
-
external_event_task: task.
|
|
1609
|
+
external_event_task: task.CancellableTask = task.CancellableTask()
|
|
1573
1610
|
event_name = name.casefold()
|
|
1574
1611
|
event_list = self._received_events.get(event_name, None)
|
|
1575
1612
|
if event_list:
|
|
@@ -1583,6 +1620,19 @@ class _RuntimeOrchestrationContext(task.OrchestrationContext):
|
|
|
1583
1620
|
task_list = []
|
|
1584
1621
|
self._pending_events[event_name] = task_list
|
|
1585
1622
|
task_list.append(external_event_task)
|
|
1623
|
+
|
|
1624
|
+
def _cancel_wait() -> None:
|
|
1625
|
+
waiting_tasks = self._pending_events.get(event_name)
|
|
1626
|
+
if waiting_tasks is None:
|
|
1627
|
+
return
|
|
1628
|
+
try:
|
|
1629
|
+
waiting_tasks.remove(external_event_task)
|
|
1630
|
+
except ValueError:
|
|
1631
|
+
return
|
|
1632
|
+
if not waiting_tasks:
|
|
1633
|
+
del self._pending_events[event_name]
|
|
1634
|
+
|
|
1635
|
+
external_event_task.set_cancel_handler(_cancel_wait)
|
|
1586
1636
|
return external_event_task
|
|
1587
1637
|
|
|
1588
1638
|
def continue_as_new(self, new_input, *, save_events: bool = False) -> None:
|
|
@@ -1625,9 +1675,11 @@ class _OrchestrationExecutor:
|
|
|
1625
1675
|
registry: _Registry,
|
|
1626
1676
|
logger: logging.Logger,
|
|
1627
1677
|
persisted_orch_span_id: Optional[str] = None,
|
|
1678
|
+
maximum_timer_interval: Optional[timedelta] = DEFAULT_MAXIMUM_TIMER_INTERVAL,
|
|
1628
1679
|
):
|
|
1629
1680
|
self._registry = registry
|
|
1630
1681
|
self._logger = logger
|
|
1682
|
+
self._maximum_timer_interval = maximum_timer_interval
|
|
1631
1683
|
self._is_suspended = False
|
|
1632
1684
|
self._suspended_events: list[pb.HistoryEvent] = []
|
|
1633
1685
|
self._persisted_orch_span_id = persisted_orch_span_id
|
|
@@ -1661,7 +1713,11 @@ class _OrchestrationExecutor:
|
|
|
1661
1713
|
"The new history event list must have at least one event in it."
|
|
1662
1714
|
)
|
|
1663
1715
|
|
|
1664
|
-
ctx = _RuntimeOrchestrationContext(
|
|
1716
|
+
ctx = _RuntimeOrchestrationContext(
|
|
1717
|
+
instance_id,
|
|
1718
|
+
self._registry,
|
|
1719
|
+
maximum_timer_interval=self._maximum_timer_interval,
|
|
1720
|
+
)
|
|
1665
1721
|
try:
|
|
1666
1722
|
# Rebuild local state by replaying old history into the orchestrator function
|
|
1667
1723
|
self._logger.debug(
|
|
@@ -1818,6 +1874,12 @@ class _OrchestrationExecutor:
|
|
|
1818
1874
|
f"{ctx.instance_id}: Ignoring unexpected timerFired event with ID = {timer_id}."
|
|
1819
1875
|
)
|
|
1820
1876
|
return
|
|
1877
|
+
if not isinstance(timer_task, task.TimerTask):
|
|
1878
|
+
if not ctx._is_replaying:
|
|
1879
|
+
self._logger.warning(
|
|
1880
|
+
f"{ctx.instance_id}: Ignoring timerFired event with non-timer task ID = {timer_id}."
|
|
1881
|
+
)
|
|
1882
|
+
return
|
|
1821
1883
|
# Emit timer span with backdated start time (skip during replay)
|
|
1822
1884
|
if not ctx.is_replaying:
|
|
1823
1885
|
timer_info = self._timer_fire_at.get(timer_id)
|
|
@@ -1829,27 +1891,39 @@ class _OrchestrationExecutor:
|
|
|
1829
1891
|
scheduled_time_ns=created_ns,
|
|
1830
1892
|
parent_trace_context=ctx._orchestration_trace_context or ctx._parent_trace_context,
|
|
1831
1893
|
)
|
|
1832
|
-
timer_task.
|
|
1833
|
-
if
|
|
1834
|
-
|
|
1894
|
+
next_fire_at = timer_task._handle_timer_fired(event.timerFired.fireAt.ToDatetime())
|
|
1895
|
+
if next_fire_at is not None:
|
|
1896
|
+
id = ctx.next_sequence_number()
|
|
1897
|
+
new_action = ph.new_create_timer_action(id, next_fire_at)
|
|
1898
|
+
ctx._pending_tasks[id] = timer_task
|
|
1899
|
+
ctx._pending_actions[id] = new_action
|
|
1900
|
+
|
|
1901
|
+
def _cancel_timer() -> None:
|
|
1902
|
+
ctx._pending_actions.pop(id, None)
|
|
1903
|
+
ctx._pending_tasks.pop(id, None)
|
|
1904
|
+
|
|
1905
|
+
timer_task.set_cancel_handler(_cancel_timer)
|
|
1906
|
+
else:
|
|
1907
|
+
if timer_task._retryable_parent is not None:
|
|
1908
|
+
activity_action = timer_task._retryable_parent._action
|
|
1835
1909
|
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1910
|
+
if not timer_task._retryable_parent._is_sub_orch:
|
|
1911
|
+
cur_task = activity_action.scheduleTask
|
|
1912
|
+
instance_id = None
|
|
1913
|
+
else:
|
|
1914
|
+
cur_task = activity_action.createSubOrchestration
|
|
1915
|
+
instance_id = cur_task.instanceId
|
|
1916
|
+
ctx.call_activity_function_helper(
|
|
1917
|
+
id=activity_action.id,
|
|
1918
|
+
activity_function=cur_task.name,
|
|
1919
|
+
input=cur_task.input.value,
|
|
1920
|
+
retry_policy=timer_task._retryable_parent._retry_policy,
|
|
1921
|
+
is_sub_orch=timer_task._retryable_parent._is_sub_orch,
|
|
1922
|
+
instance_id=instance_id,
|
|
1923
|
+
fn_task=timer_task._retryable_parent,
|
|
1924
|
+
)
|
|
1839
1925
|
else:
|
|
1840
|
-
|
|
1841
|
-
instance_id = cur_task.instanceId
|
|
1842
|
-
ctx.call_activity_function_helper(
|
|
1843
|
-
id=activity_action.id,
|
|
1844
|
-
activity_function=cur_task.name,
|
|
1845
|
-
input=cur_task.input.value,
|
|
1846
|
-
retry_policy=timer_task._retryable_parent._retry_policy,
|
|
1847
|
-
is_sub_orch=timer_task._retryable_parent._is_sub_orch,
|
|
1848
|
-
instance_id=instance_id,
|
|
1849
|
-
fn_task=timer_task._retryable_parent,
|
|
1850
|
-
)
|
|
1851
|
-
else:
|
|
1852
|
-
ctx.resume()
|
|
1926
|
+
ctx.resume()
|
|
1853
1927
|
elif event.HasField("taskScheduled"):
|
|
1854
1928
|
# This history event confirms that the activity execution was successfully scheduled.
|
|
1855
1929
|
# Remove the taskScheduled event from the pending action list so we don't schedule it again.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/entities/entity_instance_id.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/entity_state_shim.py
RENAMED
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/grpc_interceptor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/internal/orchestrator_service_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask/testing/in_memory_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev28 → durabletask-1.3.0.dev29}/durabletask.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|