Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

context.df.isReplaying is always undefined #564

Open
Kosmonaft opened this issue Dec 20, 2023 · 3 comments
Open

context.df.isReplaying is always undefined #564

Kosmonaft opened this issue Dec 20, 2023 · 3 comments
Labels
bug Something isn't working P1 Priority 1 item

Comments

@Kosmonaft
Copy link

Describe the bug
context.df.isReplaying is always undefined.
I'm able to get isReplaying from context.triggerMetadata.isReplaying

Investigative information

To Reproduce

  1. Create new durable functions project in programming model v4 (eg. follow official Azure tutorial: https://learn.microsoft.com/en-us/azure/azure-functions/durable/quickstart-ts-vscode?pivots=nodejs-model-v4))
  2. in src/functions/hello.ts add the following code on the beginning of const helloOrchestrator: OrchestrationHandler
    context.log('-----context.df.isReplaying:', context.df.isReplaying); context.log('-----context.triggerMetadata.isReplaying:', context.triggerMetadata.isReplaying);
  3. Call the orchestrator

Expected behavior
Expecting to get boolean value from context.df.isReplaying

Actual behavior
context.df.isReplaying is always undefined

Screenshots
image

Known workarounds
I believe the correct value for isReplaying is coming context.triggerMetadata.isReplaying

Additional context
I’m trying to build an Azure durable function in TypeScript using the programming model v4.
Sadly during my tests I noticed that every context.log is logged multiple times.

After digging in, I’ve learned that it’s one of the weirdest things in the Durable functions.
However based on Azure documentation about App Logging I should be able to resolve this issue by simply checking if the context.df.isReplaying is false. eg:
if (!context.df.isReplaying) context.log("Calling F1.");

Believe me or not but this is not working.
Every single time I’m getting that context.df.isReplaying is undefined.

After few experiments I’m able to get the correct value for isReplaying but from context.triggerMetadata.isReplaying.
I started thinking that there should be something wrong with how I implemented my durable functions, however even the durable functions starter from Azure documentation is giving me the same result

Is this a bug in the Azure programming model v4 or their documentation is not updated?
Do you think I could rely on context.triggerMetadata.isReplaying?

@lilyjma lilyjma added bug Something isn't working P1 Priority 1 item and removed Needs: Triage 🔍 labels Jan 4, 2024
@WhataHeckinName
Copy link

I've encountered this as well but I've discovered more information when testing with the basic "Hello" activity template. Instead of yielding to an array, I changed it to yield to it's own variable.

const activityName = "DurableFunctionsHelloActivity";
const orchestratorName = "DurableFunctionsOrchestrator";

export const durableFunctionsOrchestrator: OrchestrationHandler = function* (context: OrchestrationContext) {
    logIfNotReplaying(context, `Received request to orchestrator ${orchestratorName}`);

    const output1 = yield context.df.callActivity(activityName, "Tokyo"); // line 7
    const output2 = yield context.df.callActivity(activityName, "Seattle"); // line 8
    const output3 = yield context.df.callActivity(activityName, "Cairo"); // line 9

    logIfNotReplaying(context, `Processed orchestrator with outputs ${output1} ${output2} ${output3}`);

    return [output1, output2, output3];
};

const logIfNotReplaying = (context: OrchestrationContext, message: string): void => {
    const isReplayString = context.triggerMetadata.isReplaying as string;
    if (isReplayString == 'False') {
        context.log(message);
    }
};

df.app.orchestration(orchestratorName, durableFunctionsOrchestrator);

context.triggerMetadata.isReplaying is 'False' until after the first callActivity and then for the rest of the orchestrator's life (line 8 and on) it is set to 'True' regardless of whether it has handled the next activity yet or not. So this is not a reliable replacement.

context.df.isReplaying is undefined until after a callActivity processes. So put a break point on line 8. First time that break point is hit, it changes to false. After the 2nd time it hits the break point (meaning 2nd callActivity processed and intends to go to the 3rd callActivity) then it changes to true but only during the breakpoint on line 8. A second break point on line 9 would show the value immediately go back to false.

So there's definitely a bug to this context.df.isReplaying value. I've confirmed this with durable-functions version 3.0.0 and latest.

@collinstevens
Copy link

collinstevens commented Feb 18, 2025

@WhataHeckinName for what it's worth, here is the bug.

The Task type is returned from all the activity functions on the orchestration context and isPlayed is never initialized on TaskBase.

public isPlayed: boolean;

Before an orchestration can execute, the executor is built. A dummy Task is created which is used to "kick-start" the generator (your orchestration function), but it doesn't set the isPlayed field.

export class NoOpTask extends TaskBase {
constructor() {
super(false, "noOp");
}
}

this.currentTask = new NoOpTask();
this.currentTask.setValue(false, undefined);

The dummy task is used to set isReplaying on the context until the orchestration hits the next Task return (callActivity, waitForExternalEvent, createTimer).

const currentTask: TaskBase = this.currentTask;
this.context.isReplaying = currentTask.isPlayed;

It's mind blowing to me this hasn't been fixed. Considering the lack of activity in this repository, it basically looks abandoned.

Edit: it's actually not mind blowing considering the other durable function repositories are similarly abandoned with regards to open issues.

@steffennilsen
Copy link

I hit this issue yesterday after coming back to some old code. Thanks to the investigation done here I was able to work around it by starting by orchestrator with a short 1 second sleep

    // https://github.com/Azure/azure-functions-durable-js/issues/564
    yield context.df.createTimer(
        luxon.DateTime.now().plus({ seconds: 1 }).toJSDate(),
    );

Not exactly ideal but rewriting my existing tooling that consumes the logs is not something I have time for right now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working P1 Priority 1 item
Projects
None yet
Development

No branches or pull requests

5 participants