Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ to include examples, links to docs, or any other relevant information.

### Added

- Added the experimental `Worker` `patch_activation_callback` option, allowing workers
to decide whether a first non-replay `workflow.patched` call should activate a patch
during rolling deployments.

### Changed

### Deprecated
Expand Down
2 changes: 2 additions & 0 deletions temporalio/worker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
WorkerDeploymentConfig,
)
from ._workflow_instance import (
PatchActivationInput,
UnsandboxedWorkflowRunner,
WorkflowInstance,
WorkflowInstanceDetails,
Expand All @@ -77,6 +78,7 @@
"PollerBehavior",
"PollerBehaviorSimpleMaximum",
"PollerBehaviorAutoscaling",
"PatchActivationInput",
# Interceptor base classes
"Interceptor",
"ActivityInboundInterceptor",
Expand Down
1 change: 1 addition & 0 deletions temporalio/worker/_replayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ def on_eviction_hook(
workflow_failure_exception_types=self._config.get(
"workflow_failure_exception_types", []
),
patch_activation_callback=None,
debug_mode=self._config.get("debug_mode", False),
metric_meter=runtime.metric_meter,
on_eviction_hook=on_eviction_hook,
Expand Down
15 changes: 14 additions & 1 deletion temporalio/worker/_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@
_DEFAULT_WORKFLOW_TASK_EXTERNAL_STORAGE_CONCURRENCY,
_WorkflowWorker,
)
from ._workflow_instance import UnsandboxedWorkflowRunner, WorkflowRunner
from ._workflow_instance import (
PatchActivationInput,
UnsandboxedWorkflowRunner,
WorkflowRunner,
)
from .workflow_sandbox import SandboxedWorkflowRunner

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -135,6 +139,7 @@ def __init__(
use_worker_versioning: bool = False,
disable_safe_workflow_eviction: bool = False,
deployment_config: WorkerDeploymentConfig | None = None,
patch_activation_callback: Callable[[PatchActivationInput], bool] | None = None,
workflow_task_poller_behavior: PollerBehavior = PollerBehaviorSimpleMaximum(
maximum=5
),
Expand Down Expand Up @@ -307,6 +312,11 @@ def __init__(
deployment_config: Deployment config for the worker. Exclusive with ``build_id`` and
``use_worker_versioning``.
WARNING: This is an experimental feature and may change in the future.
patch_activation_callback: Experimental callback to decide whether the first
non-replay call to :py:func:`workflow.patched<temporalio.workflow.patched>`
for a patch ID should activate that patch. The callback receives a
:py:class:`PatchActivationInput` and must return ``True`` to activate
the patch or ``False`` to leave it inactive.
workflow_task_poller_behavior: Specify the behavior of workflow task polling.
Defaults to a 5-poller maximum.
activity_task_poller_behavior: Specify the behavior of activity task polling.
Expand Down Expand Up @@ -368,6 +378,7 @@ def __init__(
use_worker_versioning=use_worker_versioning,
disable_safe_workflow_eviction=disable_safe_workflow_eviction,
deployment_config=deployment_config,
patch_activation_callback=patch_activation_callback,
workflow_task_poller_behavior=workflow_task_poller_behavior,
activity_task_poller_behavior=activity_task_poller_behavior,
nexus_task_poller_behavior=nexus_task_poller_behavior,
Expand Down Expand Up @@ -528,6 +539,7 @@ def check_activity(activity: str):
workflow_failure_exception_types=config[
"workflow_failure_exception_types"
], # type: ignore[reportTypedDictNotRequiredAccess]
patch_activation_callback=config.get("patch_activation_callback"),
debug_mode=config["debug_mode"], # type: ignore[reportTypedDictNotRequiredAccess]
disable_eager_activity_execution=config[
"disable_eager_activity_execution"
Expand Down Expand Up @@ -982,6 +994,7 @@ class WorkerConfig(TypedDict, total=False):
use_worker_versioning: bool
disable_safe_workflow_eviction: bool
deployment_config: WorkerDeploymentConfig | None
patch_activation_callback: Callable[[PatchActivationInput], bool] | None
workflow_task_poller_behavior: PollerBehavior
activity_task_poller_behavior: PollerBehavior
nexus_task_poller_behavior: PollerBehavior
Expand Down
4 changes: 4 additions & 0 deletions temporalio/worker/_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
WorkflowInterceptorClassInput,
)
from ._workflow_instance import (
PatchActivationInput,
WorkflowInstance,
WorkflowInstanceDetails,
WorkflowRunner,
Expand Down Expand Up @@ -78,6 +79,7 @@ def __init__(
data_converter: temporalio.converter.DataConverter,
interceptors: Sequence[Interceptor],
workflow_failure_exception_types: Sequence[type[BaseException]],
patch_activation_callback: Callable[[PatchActivationInput], bool] | None,
debug_mode: bool,
disable_eager_activity_execution: bool,
metric_meter: temporalio.common.MetricMeter,
Expand Down Expand Up @@ -145,6 +147,7 @@ def __init__(
)

self._workflow_failure_exception_types = workflow_failure_exception_types
self._patch_activation_callback = patch_activation_callback
self._running_workflows: dict[str, _RunningWorkflow] = {}
self._disable_eager_activity_execution = disable_eager_activity_execution
self._on_eviction_hook = on_eviction_hook
Expand Down Expand Up @@ -798,6 +801,7 @@ def _create_workflow_instance(
extern_functions=self._extern_functions,
disable_eager_activity_execution=self._disable_eager_activity_execution,
worker_level_failure_exception_types=self._workflow_failure_exception_types,
patch_activation_callback=self._patch_activation_callback,
last_completion_result=init.last_completion_result,
last_failure=last_failure,
)
Expand Down
25 changes: 24 additions & 1 deletion temporalio/worker/_workflow_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ def set_worker_level_failure_exception_types(
pass


@dataclass(frozen=True)
class PatchActivationInput:
"""Input for the experimental worker patch activation callback."""

workflow_info: temporalio.workflow.Info
"""Information about the workflow execution calling ``patched``."""

patch_id: str
"""Patch ID passed to ``patched``."""


@dataclass(frozen=True)
class WorkflowInstanceDetails:
"""Immutable details for creating a workflow instance."""
Expand All @@ -144,6 +155,7 @@ class WorkflowInstanceDetails:
extern_functions: Mapping[str, Callable]
disable_eager_activity_execution: bool
worker_level_failure_exception_types: Sequence[type[BaseException]]
patch_activation_callback: Callable[[PatchActivationInput], bool] | None
last_completion_result: temporalio.api.common.v1.Payloads
last_failure: Failure | None

Expand Down Expand Up @@ -264,6 +276,7 @@ def __init__(self, det: WorkflowInstanceDetails) -> None:
self._worker_level_failure_exception_types = (
det.worker_level_failure_exception_types
)
self._patch_activation_callback = det.patch_activation_callback
self._primary_task: asyncio.Task[None] | None = None
self._time_ns = 0
self._cancel_reason: str | None = None
Expand Down Expand Up @@ -1363,7 +1376,17 @@ def workflow_patch(self, id: str, *, deprecated: bool) -> bool:
if use_patch is not None:
return use_patch

use_patch = not self._is_replaying or id in self._patches_notified
if deprecated or self._is_replaying or id in self._patches_notified:
use_patch = not self._is_replaying or id in self._patches_notified
elif self._patch_activation_callback is not None:
with self._as_read_only(in_query_or_validator=False):
use_patch = self._patch_activation_callback(
PatchActivationInput(workflow_info=self._info, patch_id=id)
)
if type(use_patch) is not bool:
raise TypeError("Patch activation callback must return true or false")
else:
use_patch = True
self._patches_memoized[id] = use_patch
if use_patch:
command = self._add_command()
Expand Down
1 change: 1 addition & 0 deletions temporalio/worker/workflow_sandbox/_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def prepare_workflow(self, defn: temporalio.workflow._Definition) -> None:
extern_functions={},
disable_eager_activity_execution=False,
worker_level_failure_exception_types=self._worker_level_failure_exception_types,
patch_activation_callback=None,
last_completion_result=Payloads(),
last_failure=Failure(),
),
Expand Down
Loading
Loading