Skip to content

Commit cd6994e

Browse files
YunchuWangCopilot
andcommitted
Address on-demand sandbox review comments
Make declaration helpers private, fail empty worker profiles, keep still-running registration thread handles, add worker typing, and add Bash examples to the sample README. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5094781 commit cd6994e

5 files changed

Lines changed: 132 additions & 54 deletions

File tree

durabletask-azuremanaged/durabletask/azuremanaged/preview/on_demand_sandbox/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from azure.core.credentials import TokenCredential
88

99
from durabletask.azuremanaged.preview.on_demand_sandbox.declarations import (
10-
build_profile_on_demand_sandbox_activity_declarations,
10+
_build_profile_on_demand_sandbox_activity_declarations,
1111
)
1212
from durabletask.azuremanaged.preview.on_demand_sandbox.transport import (
1313
OnDemandSandboxActivitiesGrpcTransport,
@@ -42,7 +42,7 @@ def close(self) -> None:
4242

4343
def enable_on_demand_sandbox_activities(self) -> None:
4444
"""Declare all configured on-demand sandbox worker profiles with Durable Task Scheduler."""
45-
declarations = build_profile_on_demand_sandbox_activity_declarations()
45+
declarations = _build_profile_on_demand_sandbox_activity_declarations()
4646
if not declarations:
4747
raise ValueError("No configured on-demand sandbox activities were found.")
4848

durabletask-azuremanaged/durabletask/azuremanaged/preview/on_demand_sandbox/declarations.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,17 @@ def decorator(cls: type) -> type:
6969
if callable(configure):
7070
configure(options)
7171

72+
if not _resolve_activity_names(options.activity_names):
73+
raise ValueError(
74+
f"On-demand sandbox worker profile '{normalized_profile}' must declare at least one activity.")
75+
7276
_worker_profiles[normalized_profile] = options
7377
return cls
7478

7579
return decorator
7680

7781

78-
def resolve_activity_names(activity_names: str | Iterable[str]) -> list[str]:
82+
def _resolve_activity_names(activity_names: str | Iterable[str]) -> list[str]:
7983
resolved: list[str] = []
8084
seen: set[str] = set()
8185
names = [activity_names] if isinstance(activity_names, str) else activity_names
@@ -87,7 +91,7 @@ def resolve_activity_names(activity_names: str | Iterable[str]) -> list[str]:
8791
return resolved
8892

8993

90-
def build_on_demand_sandbox_activity_declaration(
94+
def _build_on_demand_sandbox_activity_declaration(
9195
*,
9296
activity_names: str | Iterable[str],
9397
scheduler_managed_identity_client_id: str,
@@ -107,7 +111,7 @@ def build_on_demand_sandbox_activity_declaration(
107111
such as "myregistry.azurecr.io/workers/hello:1.0" or
108112
"myregistry.azurecr.io/workers/hello@sha256:0123456789abcdef...".
109113
"""
110-
resolved_activity_names = resolve_activity_names(activity_names)
114+
resolved_activity_names = _resolve_activity_names(activity_names)
111115
if not resolved_activity_names:
112116
raise ValueError("On-demand sandbox activity declaration requires at least one activity name.")
113117

@@ -150,24 +154,22 @@ def build_on_demand_sandbox_activity_declaration(
150154
return declaration
151155

152156

153-
def build_profile_on_demand_sandbox_activity_declarations() -> list[pb.OnDemandSandboxActivityDeclaration]:
157+
def _build_profile_on_demand_sandbox_activity_declarations() -> list[pb.OnDemandSandboxActivityDeclaration]:
154158
"""Build on-demand sandbox declarations from worker profile configuration."""
155159
declarations: list[pb.OnDemandSandboxActivityDeclaration] = []
156160
activity_owners: dict[str, str] = {}
157161
for profile in _worker_profiles.values():
158-
activity_names = resolve_activity_names(profile.activity_names)
159-
if not activity_names:
160-
continue
162+
activity_names = _resolve_activity_names(profile.activity_names)
161163

162164
for activity_name in activity_names:
163165
existing_profile = activity_owners.get(activity_name)
164166
if existing_profile and existing_profile != profile.worker_profile_id:
165167
raise ValueError(
166168
f"On-demand sandbox activity '{activity_name}' is assigned to both worker profile "
167-
f"'{existing_profile}' and '{profile.worker_profile_id}'.")
169+
f"'{existing_profile}' and '{profile.worker_profile_id}'.")
168170
activity_owners[activity_name] = profile.worker_profile_id
169171

170-
declarations.append(build_on_demand_sandbox_activity_declaration(
172+
declarations.append(_build_on_demand_sandbox_activity_declaration(
171173
activity_names=activity_names,
172174
worker_profile_id=profile.worker_profile_id,
173175
container_image=profile.container_image,
@@ -183,7 +185,7 @@ def build_profile_on_demand_sandbox_activity_declarations() -> list[pb.OnDemandS
183185
return declarations
184186

185187

186-
def build_on_demand_sandbox_worker_start(
188+
def _build_on_demand_sandbox_worker_start(
187189
*,
188190
taskhub: str,
189191
worker_profile_id: str,
@@ -200,7 +202,7 @@ def build_on_demand_sandbox_worker_start(
200202
if max_activities_count <= 0:
201203
raise ValueError("On-demand sandbox activity worker max activity count must be greater than zero.")
202204

203-
resolved_activity_names = resolve_activity_names(activity_names)
205+
resolved_activity_names = _resolve_activity_names(activity_names)
204206
if not resolved_activity_names:
205207
raise ValueError("On-demand sandbox activity worker registration requires at least one registered activity.")
206208

@@ -215,7 +217,7 @@ def build_on_demand_sandbox_worker_start(
215217
return message
216218

217219

218-
def build_on_demand_sandbox_worker_heartbeat(active_activities_count: int) -> pb.OnDemandSandboxActivityWorkerMessage:
220+
def _build_on_demand_sandbox_worker_heartbeat(active_activities_count: int) -> pb.OnDemandSandboxActivityWorkerMessage:
219221
if active_activities_count < 0:
220222
raise ValueError("On-demand sandbox activity worker active activity count cannot be negative.")
221223

durabletask-azuremanaged/durabletask/azuremanaged/preview/on_demand_sandbox/worker.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
from durabletask.azuremanaged.preview.on_demand_sandbox.declarations import (
1313
DEFAULT_MAX_CONCURRENT_ACTIVITIES,
1414
DEFAULT_WORKER_PROFILE_ID,
15-
build_on_demand_sandbox_worker_heartbeat,
16-
build_on_demand_sandbox_worker_start,
17-
resolve_activity_names,
15+
_build_on_demand_sandbox_worker_heartbeat,
16+
_build_on_demand_sandbox_worker_start,
17+
_resolve_activity_names,
1818
)
1919
from durabletask.azuremanaged.preview.on_demand_sandbox.transport import (
2020
OnDemandSandboxActivitiesGrpcTransport,
@@ -68,7 +68,7 @@ def __init__(self):
6868
self._on_demand_sandbox_active_activities = 0
6969
self._on_demand_sandbox_active_activities_lock = threading.Lock()
7070

71-
def add_activity(self, fn):
71+
def add_activity(self, fn) -> str:
7272
activity_name = super().add_activity(fn)
7373
self._on_demand_sandbox_activity_names.append(activity_name)
7474
return activity_name
@@ -91,7 +91,7 @@ def _durabletask_on_activity_execution_completed(self, req) -> None:
9191
self._on_demand_sandbox_active_activities = max(0, self._on_demand_sandbox_active_activities - 1)
9292

9393
def _configure_on_demand_sandbox_activity_filters(self) -> None:
94-
activity_names = resolve_activity_names(self._on_demand_sandbox_activity_names)
94+
activity_names = _resolve_activity_names(self._on_demand_sandbox_activity_names)
9595
if not activity_names:
9696
raise RuntimeError(
9797
"On-demand sandbox worker requires at least one registered activity before it can register.")
@@ -112,8 +112,13 @@ def _start_on_demand_sandbox_registration(self) -> None:
112112

113113
def _stop_on_demand_sandbox_registration(self) -> None:
114114
self._on_demand_sandbox_registration_stop.set()
115-
if self._on_demand_sandbox_registration_thread is not None:
116-
self._on_demand_sandbox_registration_thread.join(timeout=10)
115+
thread = self._on_demand_sandbox_registration_thread
116+
if thread is not None:
117+
thread.join(timeout=10)
118+
if thread.is_alive():
119+
self._on_demand_sandbox_logger.warning(
120+
"On-demand sandbox activity worker registration thread did not stop within 10 seconds.")
121+
return
117122
self._on_demand_sandbox_registration_thread = None
118123

119124
def _run_on_demand_sandbox_registration_loop(self) -> None:
@@ -139,7 +144,7 @@ def _run_on_demand_sandbox_registration_loop(self) -> None:
139144
retry_delay = min(retry_delay * 2, 30.0)
140145

141146
def _registration_messages(self) -> Iterator:
142-
yield build_on_demand_sandbox_worker_start(
147+
yield _build_on_demand_sandbox_worker_start(
143148
taskhub=self._on_demand_sandbox_taskhub,
144149
worker_profile_id=self._on_demand_sandbox_worker_profile_id,
145150
max_activities_count=self._on_demand_sandbox_max_activities,
@@ -151,7 +156,7 @@ def _registration_messages(self) -> Iterator:
151156
self._on_demand_sandbox_heartbeat_interval_seconds):
152157
with self._on_demand_sandbox_active_activities_lock:
153158
active_count = self._on_demand_sandbox_active_activities
154-
yield build_on_demand_sandbox_worker_heartbeat(active_count)
159+
yield _build_on_demand_sandbox_worker_heartbeat(active_count)
155160

156161

157162
def _resolve_taskhub() -> str:

examples/on_demand_sandbox/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ $env:DTS_ON_DEMAND_SANDBOX_IMAGE_PULL_UMI_CLIENT_ID = "<image-pull UMI client ID
3030
$env:DTS_ON_DEMAND_SANDBOX_SCHEDULER_UMI_CLIENT_ID = "<scheduler UMI client ID>"
3131
```
3232

33+
```bash
34+
export DTS_ENDPOINT="<scheduler endpoint>"
35+
export DTS_TASK_HUB="<task hub name>"
36+
export DTS_WORKER_PROFILE_ID="default"
37+
export DTS_ON_DEMAND_SANDBOX_CONTAINER_IMAGE="<container image reference>"
38+
export DTS_ON_DEMAND_SANDBOX_IMAGE_PULL_UMI_CLIENT_ID="<image-pull UMI client ID>"
39+
export DTS_ON_DEMAND_SANDBOX_SCHEDULER_UMI_CLIENT_ID="<scheduler UMI client ID>"
40+
```
41+
3342
After pushing the remote worker image, set `DTS_ON_DEMAND_SANDBOX_CONTAINER_IMAGE` to
3443
the pushed image reference. `RemoteWorkerProfile.configure()` declares CPU,
3544
memory, max concurrency, customer environment variables, and on-demand sandbox activity
@@ -55,6 +64,14 @@ docker build `
5564
docker push <public container image reference>
5665
```
5766

67+
```bash
68+
docker build \
69+
-f examples/on_demand_sandbox/Containerfile \
70+
-t <public container image reference> \
71+
.
72+
docker push <public container image reference>
73+
```
74+
5875
Private preview requires the image to be publicly pullable by the sandbox platform.
5976

6077
## Run the declarer app
@@ -65,12 +82,20 @@ Install local packages from the repository root:
6582
pip install -e . -e .\durabletask-azuremanaged
6683
```
6784

85+
```bash
86+
pip install -e . -e ./durabletask-azuremanaged
87+
```
88+
6889
Then run:
6990

7091
```powershell
7192
python examples\on_demand_sandbox\main_app.py
7293
```
7394

95+
```bash
96+
python examples/on_demand_sandbox/main_app.py
97+
```
98+
7499
The declarer app registers the on-demand sandbox activity metadata, starts
75100
`hello_orchestrator`, and the remote worker sandbox executes `remote_hello`.
76101
The result includes `ON_DEMAND_SANDBOX_SAMPLE_MARKER=on-demand-sandbox-python-sample-marker`,

0 commit comments

Comments
 (0)