Skip to content

Commit 9a95fef

Browse files
Add debugpy bundle import passthrough when debug mode is enabled. debugpy is used by the Microsoft authored VSCode python debugger (#1249)
1 parent cb9c06f commit 9a95fef

2 files changed

Lines changed: 76 additions & 5 deletions

File tree

temporalio/worker/_workflow.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from temporalio.api.enums.v1 import WorkflowTaskFailedCause
3030
from temporalio.bridge.worker import PollShutdownError
3131
from temporalio.converter import StorageDriverStoreContext, StorageDriverWorkflowInfo
32+
from temporalio.worker.workflow_sandbox._runner import SandboxedWorkflowRunner
3233

3334
from . import _command_aware_visitor
3435
from ._interceptor import (
@@ -85,6 +86,9 @@ def __init__(
8586
encode_headers: bool,
8687
max_workflow_task_external_storage_concurrency: int,
8788
) -> None:
89+
# Debug mode is enabled if specified or if the TEMPORAL_DEBUG env var is truthy
90+
debug_mode = debug_mode or bool(os.environ.get("TEMPORAL_DEBUG"))
91+
8892
self._bridge_worker = bridge_worker
8993
self._namespace = namespace
9094
self._task_queue = task_queue
@@ -96,7 +100,19 @@ def __init__(
96100
)
97101
)
98102
self._workflow_task_executor_user_provided = workflow_task_executor is not None
103+
104+
# If debug mode is enabled, ensure that the debugpy (https://github.com/microsoft/debugpy)
105+
# import is added as a passthrough
106+
if debug_mode and isinstance(workflow_runner, SandboxedWorkflowRunner):
107+
workflow_runner = dataclasses.replace(
108+
workflow_runner,
109+
restrictions=workflow_runner.restrictions.with_passthrough_modules(
110+
"_pydevd_bundle"
111+
),
112+
)
113+
99114
self._workflow_runner = workflow_runner
115+
100116
self._unsandboxed_workflow_runner = unsandboxed_workflow_runner
101117
self._data_converter = data_converter
102118
# Build the interceptor classes and collect extern functions
@@ -127,11 +143,9 @@ def __init__(
127143
)
128144
self._throw_after_activation: Exception | None = None
129145

130-
# If there's a debug mode or a truthy TEMPORAL_DEBUG env var, disable
131-
# deadlock detection, otherwise set to 2 seconds
132-
self._deadlock_timeout_seconds = (
133-
None if debug_mode or os.environ.get("TEMPORAL_DEBUG") else 2
134-
)
146+
# If debug mode is enabled, disable deadlock detection
147+
# otherwise set to 2 seconds
148+
self._deadlock_timeout_seconds = None if debug_mode else 2
135149

136150
# Keep track of workflows that could not be evicted
137151
self._could_not_evict_count = 0

tests/worker/test_worker.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import concurrent.futures
55
import multiprocessing
66
import multiprocessing.context
7+
import os
78
import uuid
89
from collections.abc import Awaitable, Callable, Sequence
10+
from contextlib import contextmanager
911
from datetime import timedelta
1012
from typing import Any
1113
from urllib.request import urlopen
@@ -57,6 +59,7 @@
5759
WorkerTuner,
5860
WorkflowSlotInfo,
5961
)
62+
from temporalio.worker.workflow_sandbox import SandboxedWorkflowRunner
6063
from temporalio.workflow import DynamicWorkflowConfig, VersioningIntent
6164
from tests.helpers import (
6265
assert_eventually,
@@ -1650,3 +1653,57 @@ def test_worker_config_matches_init_params():
16501653
f"Missing from config: {init_params - config_keys}. "
16511654
f"Extra in config: {config_keys - init_params}."
16521655
)
1656+
1657+
1658+
async def test_worker_debug_mode(client: Client):
1659+
worker = Worker(
1660+
client,
1661+
workflows=[SimpleWorkflow],
1662+
task_queue=f"task-queue-{uuid.uuid4()}",
1663+
)
1664+
assert worker._workflow_worker
1665+
assert worker._workflow_worker._deadlock_timeout_seconds == 2
1666+
assert isinstance(worker._workflow_worker._workflow_runner, SandboxedWorkflowRunner)
1667+
assert (
1668+
"_pydevd_bundle"
1669+
not in worker._workflow_worker._workflow_runner.restrictions.passthrough_modules
1670+
)
1671+
1672+
worker = Worker(
1673+
client,
1674+
workflows=[SimpleWorkflow],
1675+
task_queue=f"task-queue-{uuid.uuid4()}",
1676+
debug_mode=True,
1677+
)
1678+
assert worker._workflow_worker
1679+
assert worker._workflow_worker._deadlock_timeout_seconds is None
1680+
assert isinstance(worker._workflow_worker._workflow_runner, SandboxedWorkflowRunner)
1681+
assert (
1682+
"_pydevd_bundle"
1683+
in worker._workflow_worker._workflow_runner.restrictions.passthrough_modules
1684+
)
1685+
1686+
@contextmanager
1687+
def debug_envvar():
1688+
os.environ["TEMPORAL_DEBUG"] = "true"
1689+
try:
1690+
yield
1691+
finally:
1692+
os.environ.pop("TEMPORAL_DEBUG")
1693+
1694+
with debug_envvar():
1695+
worker = Worker(
1696+
client,
1697+
workflows=[SimpleWorkflow],
1698+
task_queue=f"task-queue-{uuid.uuid4()}",
1699+
)
1700+
assert worker._workflow_worker
1701+
assert worker._workflow_worker._deadlock_timeout_seconds is None
1702+
assert isinstance(
1703+
worker._workflow_worker._workflow_runner,
1704+
SandboxedWorkflowRunner,
1705+
)
1706+
assert (
1707+
"_pydevd_bundle"
1708+
in worker._workflow_worker._workflow_runner.restrictions.passthrough_modules
1709+
)

0 commit comments

Comments
 (0)