Azure / azure-functions-durable-extension

Durable Task Framework extension for Azure Functions
MIT License
717 stars 271 forks source link

Powershell Durable Function - Cannot start Durable Timer #2706

Open schnabel45 opened 1 year ago

schnabel45 commented 1 year ago

Description

If attempting to start a durable timer using boilerplate orchestration, PowerShell throws a System.NullReferenceException for the Duration, even if it's known to be valid.

Expected behavior

A durable timer should be started for the duration of the timespan passed into the call.

Actual behavior

ERROR: Object reference not set to an instance of an object. is thrown with the following stack trace.

[2023-11-15T14:12:10.945Z] ERROR: Object reference not set to an instance of an object.
[2023-11-15T14:12:10.946Z] 
[2023-11-15T14:12:10.946Z] Exception             : 
[2023-11-15T14:12:10.946Z]     Type       : System.NullReferenceException
[2023-11-15T14:12:10.946Z]     TargetSite : Void .ctor(System.TimeSpan, System.Management.Automation.SwitchParameter, System.Collections.Hashtable)
[2023-11-15T14:12:10.946Z]     Message    : Object reference not set to an instance of an object.
[2023-11-15T14:12:10.946Z]     Source     : DurableEngine
[2023-11-15T14:12:10.946Z]     HResult    : -2147467261
[2023-11-15T14:12:10.946Z]     StackTrace : 
[2023-11-15T14:12:10.946Z]    at DurableEngine.Tasks.DurableTimerTask..ctor(TimeSpan duration, SwitchParameter noWait, Hashtable privateData) in D:\a\_work\1\s\src\DurableEngine\Tasks\DurableTimerTask.cs:line 32
[2023-11-15T14:12:10.946Z]    at DurableSDK.Commands.APIs.StartDurableTimerCommand.CreateDurableTask() in D:\a\_work\1\s\src\DurableSDK\Commands\APIs\StartDurableTimerCommand.cs:line 34
[2023-11-15T14:12:10.946Z]    at DurableSDK.Commands.APIs.DurableSDKCmdlet.EndProcessing() in D:\a\_work\1\s\src\DurableSDK\Commands\APIs\DurableSDKCmdlet.cs:line 21
[2023-11-15T14:12:10.946Z]    at System.Management.Automation.Cmdlet.DoEndProcessing()
[2023-11-15T14:12:10.946Z]    at System.Management.Automation.CommandProcessorBase.Complete()
[2023-11-15T14:12:10.946Z] CategoryInfo          : NotSpecified: (:) [Start-DurableTimer], NullReferenceException
[2023-11-15T14:12:10.946Z] FullyQualifiedErrorId : System.NullReferenceException,DurableSDK.Commands.APIs.StartDurableTimerCommand
[2023-11-15T14:12:10.946Z] InvocationInfo        : 
[2023-11-15T14:12:10.946Z]     MyCommand        : Start-DurableTimer
[2023-11-15T14:12:10.946Z]     ScriptLineNumber : 8
[2023-11-15T14:12:10.946Z]     OffsetInLine     : 1
[2023-11-15T14:12:10.946Z]     HistoryId        : 1
[2023-11-15T14:12:10.946Z]     ScriptName       : /Users/ericschnabel/source/<redacted>/Functions/Hello1/run.ps1
[2023-11-15T14:12:10.946Z]     Line             : Start-DurableTimer -Duration $duration
[2023-11-15T14:12:10.946Z]                        
[2023-11-15T14:12:10.946Z]     PositionMessage  : At /Users/ericschnabel/source/<redacted>/Functions/Hello1/run.ps1:8 char:1
[2023-11-15T14:12:10.946Z]                        + Start-DurableTimer -Duration $duration
[2023-11-15T14:12:10.946Z]                        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[2023-11-15T14:12:10.946Z]     PSScriptRoot     : /Users/ericschnabel/source/<redacted>/Functions/Hello1
[2023-11-15T14:12:10.946Z]     PSCommandPath    : /Users/ericschnabel/source/<redacted>/Functions/Hello1/run.ps1
[2023-11-15T14:12:10.946Z]     InvocationName   : Start-DurableTimer
[2023-11-15T14:12:10.946Z]     CommandOrigin    : Internal
[2023-11-15T14:12:10.946Z] ScriptStackTrace      : at <ScriptBlock>, /Users/ericschnabel/source/<redacted>/Functions/Hello1/run.ps1: line 8

Relevant source code snippets

Hello1 Function

run.ps1

param($name)

"Hello $name!"

$randomTime = Get-Random -Maximum 10

$duration = New-TimeSpan -Seconds $randomTime
Start-DurableTimer -Duration $duration

"Goodbye $name"

function.json

{
  "bindings": [
    {
      "name": "name",
      "type": "activityTrigger",
      "direction": "in"
    }
  ]
}

DurableFunctionsOrchestrator1

run.ps1

param($Context)

$output = @()

$output += Invoke-DurableActivity -FunctionName 'Hello1' -Input 'Tokyo'
$output += Invoke-DurableActivity -FunctionName 'Hello1' -Input 'Seattle'
$output += Invoke-DurableActivity -FunctionName 'Hello1' -Input 'London'

$output

function.json

{
  "bindings": [
    {
      "name": "Context",
      "type": "orchestrationTrigger",
      "direction": "in"
    }
  ]
}

DurableFunctionsHttpStart1

run.ps1

using namespace System.Net

param($Request, $TriggerMetadata)

$FunctionName = $Request.Params.FunctionName
$InstanceId = Start-DurableOrchestration -FunctionName $FunctionName
Write-Host "Started orchestration with ID = '$InstanceId'"

$Response = New-DurableOrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId
Push-OutputBinding -Name Response -Value $Response

function.json

{
  "bindings": [
    {
      "authLevel": "ANONYMOUS",
      "name": "Request",
      "type": "httpTrigger",
      "direction": "in",
      "route": "orchestrators/{FunctionName}",
      "methods": [
        "post",
        "get"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "Response"
    },
    {
      "name": "starter",
      "type": "durableClient",
      "direction": "in"
    }
  ]
}

Known workarounds

I've been unable to find a way around this. I've stepped through the execution during runtime and verified the $duration variable was not null, and was a valid TimeSpan object.

App Details

Screenshots

N/A

If deployed to Azure

While this has been observed in Azure as well, currently only in my local environment.

lilyjma commented 11 months ago

(transferred incorrectly previously - issue author doesn't seem to be using the standalone PoweShell SDK)

schnabel45 commented 11 months ago

(transferred incorrectly previously - issue author doesn't seem to be using the standalone PoweShell SDK)

Hi @lilyjma, just an FYI that I replicated this issue both with the standalone SDK and without. I opened this issue with the standalone SDK since that appears to be where development is progressing for PowerShell Functions now.

davidmrdavid commented 11 months ago

Hi @schnabel45: From what I gather, the issue seems to be that you're trying to instantiate a durable timer in an Activity function, but they should only be instantiated inside orchestrators: if you're inside an activity, you can just make your activity "sleep" for a given amount of time.

I believe that's the cause of your null pointer exception (the orchestrator internals are not initialized). We should have a better error message though. Please let me know if this answers your question. Also, the DF SDK for PowerShell is open source so if you'd like to contribute a helpful error message for this scenario.

It should be as simple as adding a null-check here and throw an informative error if OrchestrationContext is null.

schnabel45 commented 10 months ago

Hi @davidmrdavid, I was able to test based on your feedback today and you are 100% correct. I successfully got Durable Timers working within PowerShell inside of the orchestrator itself. I'll see if I can get a PR submitted for the recommended error later this week. Thank you!