Skip to content
Merged
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
6 changes: 6 additions & 0 deletions packages/durabletask-js/src/task/when-all-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export class WhenAllTask<T> extends CompositeTask<T[]> {

this._completedTasks = 0;
this._failedTasks = 0;

// An empty task list should complete immediately with an empty result
if (tasks.length === 0) {
this._result = [] as T[];
this._isComplete = true;
}
}

pendingTasks(): number {
Expand Down
3 changes: 3 additions & 0 deletions packages/durabletask-js/src/task/when-any-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { Task } from "./task";
*/
export class WhenAnyTask extends CompositeTask<Task<any>> {
constructor(tasks: Task<any>[]) {
if (tasks.length === 0) {
throw new Error("whenAny requires at least one task");
}
super(tasks);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ export class RuntimeOrchestrationContext extends OrchestrationContext {

// TODO: check if the task is null?
this._previousTask = value;

// If the yielded task is already complete (e.g., whenAll with an empty array),
// resume immediately so the generator can continue.
if (this._previousTask instanceof Task && this._previousTask.isComplete) {
await this.resume();
}
}

async resume() {
Expand Down
51 changes: 51 additions & 0 deletions packages/durabletask-js/test/orchestration_executor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,57 @@ describe("Orchestration Executor", () => {
expect(completeAction?.getOrchestrationstatus()).toEqual(pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED);
});
});

it("should complete immediately when whenAll is called with an empty task array", async () => {
const orchestrator: TOrchestrator = async function* (_ctx: OrchestrationContext): any {
const results = yield whenAll([]);
return results;
};

const registry = new Registry();
const orchestratorName = registry.addOrchestrator(orchestrator);

const oldEvents: any[] = [];
const newEvents = [newOrchestratorStartedEvent(), newExecutionStartedEvent(orchestratorName, TEST_INSTANCE_ID)];

const executor = new OrchestrationExecutor(registry, testLogger);
const result = await executor.execute(TEST_INSTANCE_ID, oldEvents, newEvents);

// The orchestration should complete immediately with an empty array result
const completeAction = getAndValidateSingleCompleteOrchestrationAction(result);
expect(completeAction?.getOrchestrationstatus()).toEqual(pb.OrchestrationStatus.ORCHESTRATION_STATUS_COMPLETED);
expect(completeAction?.getResult()?.getValue()).toEqual(JSON.stringify([]));
});

it("should complete when whenAll with empty array is followed by more work", async () => {
const hello = (_: any, name: string) => `Hello ${name}!`;

const orchestrator: TOrchestrator = async function* (ctx: OrchestrationContext): any {
const emptyResults = yield whenAll([]);
const activityResult = yield ctx.callActivity(hello, "World");
return { emptyResults, activityResult };
};

const registry = new Registry();
const orchestratorName = registry.addOrchestrator(orchestrator);
const activityName = registry.addActivity(hello);

// First execution: should schedule the activity after completing whenAll([])
const oldEvents: any[] = [];
const newEvents = [newOrchestratorStartedEvent(), newExecutionStartedEvent(orchestratorName, TEST_INSTANCE_ID)];

const executor = new OrchestrationExecutor(registry, testLogger);
const result = await executor.execute(TEST_INSTANCE_ID, oldEvents, newEvents);

// The whenAll([]) should complete, then an activity should be scheduled
expect(result.actions.length).toEqual(1);
expect(result.actions[0].hasScheduletask()).toBeTruthy();
expect(result.actions[0].getScheduletask()?.getName()).toEqual(activityName);
});

it("should throw when whenAny is called with an empty task array", () => {
expect(() => whenAny([])).toThrow("whenAny requires at least one task");
});
});

function getAndValidateSingleCompleteOrchestrationAction(
Expand Down
Loading