Skip to content

Commit 4b79790

Browse files
author
Bernd Verst
committed
Merge main into Python 3.11 migration
Resolve changelog conflicts by retaining both the Python 3.11 migration entries and the upstream orchestrator generator cleanup fixes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e6b07f3f-b3e1-49dd-9121-318968272d31
2 parents af7019b + 4b076ba commit 4b79790

6 files changed

Lines changed: 458 additions & 52 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ Azure Managed clients and workers.
1616
`ExceptionGroup`, preserving each exception and its traceback. A single callback
1717
failure is still raised directly.
1818

19+
FIXED
20+
21+
- Orchestrator generators are now closed when a dispatch ends, so cleanup in
22+
`finally` blocks runs deterministically, including for async bridges that would
23+
otherwise retain tasks, event loops, and orchestration contexts.
24+
1925
## v1.12.0
2026

2127
ADDED

‎azure-functions-durable/CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ minimum supported version for this package.
2525
failures raise the built-in `ExceptionGroup`; a single failure is still raised
2626
directly.
2727

28+
FIXED
29+
30+
- With the corresponding core `durabletask` SDK fix, orchestrator generators
31+
are closed when a dispatch ends, so cleanup in `finally` blocks runs
32+
deterministically, including for async bridges.
33+
2834
## v2.0.0rc3
2935

3036
ADDED

‎durabletask-azuremanaged/CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ preserve Azure Managed subclass types in type checkers.
1616
failures raise the built-in `ExceptionGroup`; a single failure is still raised
1717
directly.
1818

19+
FIXED
20+
21+
- With the corresponding core `durabletask` SDK fix, orchestrator generators
22+
are closed when a dispatch ends, so cleanup in `finally` blocks runs
23+
deterministically, including for async bridges.
24+
1925
## v1.12.0
2026

2127
ADDED

‎durabletask/worker.py‎

Lines changed: 68 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2289,64 +2289,80 @@ def execute(
22892289
logger=self._logger,
22902290
)
22912291
try:
2292-
# Rebuild local state by replaying old history into the orchestrator function
2293-
self._logger.debug(
2294-
f"{instance_id}: Rebuilding local state with {len(old_events)} history event..."
2295-
)
2296-
ctx._is_replaying = True # pyright: ignore[reportPrivateUsage]
2297-
for old_event in old_events:
2298-
self.process_event(ctx, old_event)
2299-
2300-
# Get new actions by executing newly received events into the orchestrator function
2301-
if self._logger.level <= logging.DEBUG:
2302-
summary = _get_new_event_summary(new_events)
2292+
try:
2293+
# Rebuild local state by replaying old history into the orchestrator function
23032294
self._logger.debug(
2304-
f"{instance_id}: Processing {len(new_events)} new event(s): {summary}"
2295+
f"{instance_id}: Rebuilding local state with {len(old_events)} history event..."
23052296
)
2306-
ctx._is_replaying = False # pyright: ignore[reportPrivateUsage]
2307-
for new_event in new_events:
2308-
self.process_event(ctx, new_event)
2309-
2310-
except pe.VersionFailureException as ex:
2311-
if self._registry.versioning and self._registry.versioning.failure_strategy == VersionFailureStrategy.FAIL:
2312-
if ex.error_details:
2313-
ctx.set_failed(ex.error_details)
2314-
else:
2315-
ctx.set_failed(ex)
2316-
elif self._registry.versioning and self._registry.versioning.failure_strategy == VersionFailureStrategy.REJECT:
2317-
raise pe.AbandonOrchestrationError
2297+
ctx._is_replaying = True # pyright: ignore[reportPrivateUsage]
2298+
for old_event in old_events:
2299+
self.process_event(ctx, old_event)
2300+
2301+
# Get new actions by executing newly received events into the orchestrator function
2302+
if self._logger.level <= logging.DEBUG:
2303+
summary = _get_new_event_summary(new_events)
2304+
self._logger.debug(
2305+
f"{instance_id}: Processing {len(new_events)} new event(s): {summary}"
2306+
)
2307+
ctx._is_replaying = False # pyright: ignore[reportPrivateUsage]
2308+
for new_event in new_events:
2309+
self.process_event(ctx, new_event)
2310+
2311+
except pe.VersionFailureException as ex:
2312+
if self._registry.versioning and self._registry.versioning.failure_strategy == VersionFailureStrategy.FAIL:
2313+
if ex.error_details:
2314+
ctx.set_failed(ex.error_details)
2315+
else:
2316+
ctx.set_failed(ex)
2317+
elif self._registry.versioning and self._registry.versioning.failure_strategy == VersionFailureStrategy.REJECT:
2318+
raise pe.AbandonOrchestrationError
23182319

2319-
except Exception as ex:
2320-
# Unhandled exceptions fail the orchestration
2321-
self._logger.debug(f"{instance_id}: Orchestration {orchestration_name} failed")
2322-
ctx.set_failed(ex)
2320+
except Exception as ex:
2321+
# Unhandled exceptions fail the orchestration
2322+
self._logger.debug(f"{instance_id}: Orchestration {orchestration_name} failed")
2323+
ctx.set_failed(ex)
23232324

2324-
if not ctx._is_complete: # pyright: ignore[reportPrivateUsage]
2325-
task_count = len(ctx._pending_tasks) # pyright: ignore[reportPrivateUsage]
2326-
event_count = len(ctx._pending_events) # pyright: ignore[reportPrivateUsage]
2327-
self._logger.info(
2328-
f"{instance_id}: Orchestrator {orchestration_name} yielded with {task_count} task(s) "
2329-
f"and {event_count} event(s) outstanding."
2330-
)
2331-
elif (
2332-
ctx._completion_status and ctx._completion_status is not pb.ORCHESTRATION_STATUS_CONTINUED_AS_NEW # pyright: ignore[reportPrivateUsage]
2333-
):
2334-
completion_status_str = ph.get_orchestration_status_str(
2335-
ctx._completion_status # pyright: ignore[reportPrivateUsage]
2336-
)
2337-
self._logger.info(
2338-
f"{instance_id}: Orchestration {orchestration_name} completed with status: {completion_status_str}"
2339-
)
2325+
if not ctx._is_complete: # pyright: ignore[reportPrivateUsage]
2326+
task_count = len(ctx._pending_tasks) # pyright: ignore[reportPrivateUsage]
2327+
event_count = len(ctx._pending_events) # pyright: ignore[reportPrivateUsage]
2328+
self._logger.info(
2329+
f"{instance_id}: Orchestrator {orchestration_name} yielded with {task_count} task(s) "
2330+
f"and {event_count} event(s) outstanding."
2331+
)
2332+
elif (
2333+
ctx._completion_status and ctx._completion_status is not pb.ORCHESTRATION_STATUS_CONTINUED_AS_NEW # pyright: ignore[reportPrivateUsage]
2334+
):
2335+
completion_status_str = ph.get_orchestration_status_str(
2336+
ctx._completion_status # pyright: ignore[reportPrivateUsage]
2337+
)
2338+
self._logger.info(
2339+
f"{instance_id}: Orchestration {orchestration_name} completed with status: {completion_status_str}"
2340+
)
23402341

2341-
actions = ctx.get_actions()
2342-
if self._logger.level <= logging.DEBUG:
2343-
self._logger.debug(
2344-
f"{instance_id}: Returning {len(actions)} action(s): {_get_action_summary(actions)}"
2342+
actions = ctx.get_actions()
2343+
if self._logger.level <= logging.DEBUG:
2344+
self._logger.debug(
2345+
f"{instance_id}: Returning {len(actions)} action(s): {_get_action_summary(actions)}"
2346+
)
2347+
return ExecutionResults(
2348+
actions=actions, encoded_custom_status=ctx._encoded_custom_status, # pyright: ignore[reportPrivateUsage]
2349+
orchestration_trace_context=ctx._orchestration_trace_context, # pyright: ignore[reportPrivateUsage]
23452350
)
2346-
return ExecutionResults(
2347-
actions=actions, encoded_custom_status=ctx._encoded_custom_status, # pyright: ignore[reportPrivateUsage]
2348-
orchestration_trace_context=ctx._orchestration_trace_context, # pyright: ignore[reportPrivateUsage]
2349-
)
2351+
finally:
2352+
# Close after building the response, so the orchestrator's finally
2353+
# blocks do not change the actions or custom status we return.
2354+
if ctx._generator is not None: # pyright: ignore[reportPrivateUsage]
2355+
try:
2356+
ctx._generator.close() # pyright: ignore[reportPrivateUsage]
2357+
except Exception:
2358+
cleanup_name = next(
2359+
(e.executionStarted.name for e in new_events if e.HasField("executionStarted")),
2360+
orchestration_name,
2361+
)
2362+
self._logger.warning(
2363+
f"{instance_id}: Failed to close orchestrator {cleanup_name} generator.",
2364+
exc_info=True,
2365+
)
23502366

23512367
def _build_rewind_result(
23522368
self,

‎requirements.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ grpcio>=1.60.0 # 1.60.0 is the version introducing protobuf 1.25.X support, newe
33
protobuf
44
pytest
55
pytest-asyncio
6+
anyio
67
pytest-cov
78
azure-identity
89
azure-functions

0 commit comments

Comments
 (0)