Azure / Azure/azure-functions-durable-js
context.df.Task.any with createTimer and waitForExternalEvent leaves orchestrator in invalid state
- Dominant language
- TypeScript
- Stars
- 142
- Forks
- 66
- Avg merge
- 3d 19h
- Merged PRs (30d)
- 4
Description
**Describe the bug**
I have this code
```ts
df.app.orchestration('queue_orchestrator', {
handler: function* (context) {
const response: 'queue_empty' | 'continue' | null = yield context.df.callActivity('pop_queue');
if (!response) {
// Activity failed retry in 1 minute
yield context.df.createTimer(new Date(context.df.currentUtcDateTime.getTime() + 1000 * 60)); // 1 minute
} else if (response === 'queue_empty') {
// No more items in queue, wait for new items
const timeoutTask = context.df.createTimer(
new Date(context.df.currentUtcDateTime.getTime() + 1000 * 60 * 60 * 5),
); // 5 hours
const hasQueueTask = context.df.waitForExternalEvent('has_queue');
yield context.df.Task.any([timeoutTask, hasQueueTask]);
}
context.df.continueAsNew(undefined);
},
});
```
1. After this code is run under function emulator 4.0.5455 on WIndows 11
```ts
const timeoutTask = context.df.createTimer(
new Date(context.df.currentUtcDateTime.getTime() + 1000 * 60 * 60 * 5),
); // 5 hours
const hasQueueTask = context.df.waitForExternalEvent('has_queue');
yield context.df.Task.any([timeoutTask, hasQueueTask]);
```
2. this code starts returning old data without calling the activity
```ts
const response: 'queue_empty' | 'continue' | null = yield context.df.callActivity('pop_queue');
```
If I change code 1. to this everything works
```ts
yield context.df.waitForExternalEvent('has_queue');
```
I think there is a bug in context.df.Task.any that it leaves orchestrator in invalid state if waitForExternalEvent is first task to finish.
**Investigative information**
- durable-functions npm module version: 3.0.1
- Language (JavaScript/TypeScript) and version: 5.2.2
- Node.js version: v18.18
**Actual behavior**
When using context.df.Task.any with context.df.createTimer & context.df.waitForExternalEvent and if context.df.waitForExternalEvent task finishes first orchestrator is left in invalid state and starts to receive old values from context.df.callActivity('pop_queue') without even calling the activity meaning code in the activity is never called.
Contributor guide
Research direction
Start with the context.df.Task.any reproduction combining createTimer and waitForExternalEvent, using the function emulator 4.0.5455 and the versions listed. Verify whether the orchestrator receives stale callActivity('pop_queue') results after the external event wins; done means the activity is invoked normally and the orchestrator remains valid after either task completes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, node.js, typescript
- Domain
- backend, distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100