Skip to content

Commit 63db777

Browse files
committed
Preserve lifecycle reasons in test backend
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 941fc052-b910-42af-93fa-4f00138813ab
1 parent 5f7ba50 commit 63db777

6 files changed

Lines changed: 48 additions & 9 deletions

File tree

.github/copilot-instructions.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,17 @@ works correctly:
224224
- If a new public API is added to the core SDK (e.g. a method on
225225
`OrchestrationContext`), confirm it is accessible through the
226226
azuremanaged package and add a test or example if appropriate.
227+
228+
## Release Coordination
229+
230+
- Package versions and inter-package minimum dependencies record the latest
231+
released compatibility contract. They are not an instruction to bump versions
232+
in a feature PR.
233+
- When a core `durabletask` API is consumed by `durabletask.azuremanaged` or
234+
`azure-functions-durable`, do not update package versions or dependency
235+
minimums in the feature PR. Document the affected package changelogs and
236+
release coordination instead.
237+
- Create a dedicated release PR after the core package has been released. It
238+
must publish `durabletask` first, then update provider dependency minimums,
239+
package versions, and release notes so resolvers cannot select a provider
240+
release with an incompatible older core package.

azure-functions-durable/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
> [!NOTE]
11+
> Release this change only in coordination with a new `durabletask` release
12+
> that contains the suspend/resume reason APIs. Publish `durabletask` first,
13+
> then update this package's minimum dependency in its dedicated release PR.
14+
1015
FIXED
1116

1217
- Fixed deprecated `DurableFunctionsClient.suspend()` and `resume()` methods

durabletask-azuremanaged/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## Unreleased
99

10+
ADDED
11+
12+
- Added optional `reason` parameters to suspend and resume operations inherited
13+
from `TaskHubGrpcClient` and `AsyncTaskHubGrpcClient`.
14+
1015
## v1.9.0
1116

1217
CHANGED

durabletask/internal/helpers.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,19 +234,23 @@ def new_event_raised_event(name: str, encoded_input: str | None = None) -> pb.Hi
234234
)
235235

236236

237-
def new_suspend_event() -> pb.HistoryEvent:
237+
def new_suspend_event(*, encoded_input: str | None = None) -> pb.HistoryEvent:
238238
return pb.HistoryEvent(
239239
eventId=-1,
240240
timestamp=timestamp_pb2.Timestamp(),
241-
executionSuspended=pb.ExecutionSuspendedEvent()
241+
executionSuspended=pb.ExecutionSuspendedEvent(
242+
input=get_string_value(encoded_input)
243+
)
242244
)
243245

244246

245-
def new_resume_event() -> pb.HistoryEvent:
247+
def new_resume_event(*, encoded_input: str | None = None) -> pb.HistoryEvent:
246248
return pb.HistoryEvent(
247249
eventId=-1,
248250
timestamp=timestamp_pb2.Timestamp(),
249-
executionResumed=pb.ExecutionResumedEvent()
251+
executionResumed=pb.ExecutionResumedEvent(
252+
input=get_string_value(encoded_input)
253+
)
250254
)
251255

252256

durabletask/testing/in_memory_backend.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,9 @@ def SuspendInstance(self, request: pb.SuspendRequest, context: grpc.ServicerCont
386386
if instance.status == pb.ORCHESTRATION_STATUS_SUSPENDED:
387387
return pb.SuspendResponse()
388388

389-
event = helpers.new_suspend_event()
389+
event = helpers.new_suspend_event(
390+
encoded_input=request.reason.value if request.HasField("reason") else None
391+
)
390392
instance.pending_events.append(event)
391393
instance.last_updated_at = datetime.now(timezone.utc)
392394
self._enqueue_orchestration(instance.instance_id)
@@ -403,7 +405,9 @@ def ResumeInstance(self, request: pb.ResumeRequest, context: grpc.ServicerContex
403405
f"Orchestration instance '{request.instanceId}' not found")
404406
return pb.ResumeResponse()
405407

406-
event = helpers.new_resume_event()
408+
event = helpers.new_resume_event(
409+
encoded_input=request.reason.value if request.HasField("reason") else None
410+
)
407411
instance.pending_events.append(event)
408412
instance.last_updated_at = datetime.now(timezone.utc)
409413
self._enqueue_orchestration(instance.instance_id)

tests/durabletask/test_orchestration_e2e.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ def orchestrator(ctx: task.OrchestrationContext, _):
275275
assert state.serialized_output == json.dumps("timed out")
276276

277277

278-
def test_suspend_and_resume():
278+
def test_suspend_and_resume_preserves_reasons_in_history():
279279
def orchestrator(ctx: task.OrchestrationContext, _):
280280
result = yield ctx.wait_for_external_event("my_event")
281281
return result
@@ -290,7 +290,7 @@ def orchestrator(ctx: task.OrchestrationContext, _):
290290
assert state is not None
291291

292292
# Suspend the orchestration and wait for it to go into the SUSPENDED state
293-
task_hub_client.suspend_orchestration(id)
293+
task_hub_client.suspend_orchestration(id, reason="maintenance")
294294
deadline = time.time() + 10
295295
while state.runtime_status == client.OrchestrationStatus.RUNNING:
296296
assert time.time() < deadline, "Timed out waiting for SUSPENDED status"
@@ -308,11 +308,18 @@ def orchestrator(ctx: task.OrchestrationContext, _):
308308
pass
309309

310310
# Resume the orchestration and wait for it to complete
311-
task_hub_client.resume_orchestration(id)
311+
task_hub_client.resume_orchestration(id, reason="maintenance complete")
312312
state = task_hub_client.wait_for_orchestration_completion(id, timeout=30)
313+
events = task_hub_client.get_orchestration_history(id)
313314
assert state is not None
314315
assert state.runtime_status == client.OrchestrationStatus.COMPLETED
315316
assert state.serialized_output == json.dumps(42)
317+
suspended_event = next(
318+
event for event in events if isinstance(event, history.ExecutionSuspendedEvent))
319+
resumed_event = next(
320+
event for event in events if isinstance(event, history.ExecutionResumedEvent))
321+
assert suspended_event.input == "maintenance"
322+
assert resumed_event.input == "maintenance complete"
316323

317324

318325
def test_terminate():

0 commit comments

Comments
 (0)