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

Prepare Worker for External DF SDK support (and a few DF bug fixes) #746

Closed
wants to merge 47 commits into from
Closed
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4423d13
separate DF SDK classes from DF worker classes
davidmrdavid Jan 29, 2022
9d73110
fix typo
davidmrdavid Jan 29, 2022
2fcbfae
DurableSDK now compiles by itself
davidmrdavid Jan 29, 2022
f43f350
Allow ExternalSDK to handle orchestration
davidmrdavid Feb 3, 2022
092e333
document next steps
davidmrdavid Feb 3, 2022
506e4f7
allow external SDK to set the user-code's input. Still need to refact…
davidmrdavid Feb 4, 2022
7a38ec8
add import module
davidmrdavid Feb 16, 2022
9b2cf42
supress traces
davidmrdavid Feb 16, 2022
47caee9
avoid nullptr
davidmrdavid Feb 16, 2022
1010f97
pass tests
davidmrdavid Feb 17, 2022
d916d58
fix E2E tests
davidmrdavid Feb 17, 2022
946ea37
develop E2E tests
davidmrdavid Feb 18, 2022
64b6c49
Enabled external durable client (#765)
davidmrdavid Feb 18, 2022
9e7cf96
bindings work
davidmrdavid Feb 18, 2022
92f113e
conditional binding intialization
davidmrdavid Feb 18, 2022
e5843ce
conditional import
davidmrdavid Feb 18, 2022
2ee6b96
Added exception handling logic
michaelpeng36 Feb 25, 2022
ae28f6e
Ensure unit tests are functioning properly
michaelpeng36 Feb 28, 2022
94f995d
Corrected unit test names
michaelpeng36 Feb 28, 2022
96f6ee7
Turned repeated variables in unit tests into static members
michaelpeng36 Feb 28, 2022
1cb30b8
Revert durableController name to durableFunctionsUtils
michaelpeng36 Mar 1, 2022
ff5102e
Merge remote-tracking branch 'origin/michaelpeng/get-durable-tests-wo…
davidmrdavid Mar 1, 2022
b8c6a6a
Fixed issue with building the worker
michaelpeng36 Mar 8, 2022
baae3cc
Fix E2E test
michaelpeng36 Mar 8, 2022
6feb7fe
Fixed unit test setup
michaelpeng36 Mar 8, 2022
94d83ae
Fixed another unit test setup
michaelpeng36 Mar 8, 2022
d11a561
Remove string representation of booleans
michaelpeng36 Mar 8, 2022
0e0c077
patch e2e test
davidmrdavid Mar 9, 2022
1c4a7ae
remove typo in toString
davidmrdavid Mar 9, 2022
dc0ed38
Merge branch 'dev' of https://github.com/Azure/azure-functions-powers…
davidmrdavid Mar 9, 2022
3bd961c
Return results from Start-DurableExternalEventListener (#685) (#753)
davidmrdavid Mar 9, 2022
caa5176
add external contrib
davidmrdavid Mar 9, 2022
932e124
add e2e test for GetTaskResult
davidmrdavid Mar 9, 2022
3d1a7b4
parametrize test
davidmrdavid Mar 9, 2022
adec8fa
patch new e2e test
davidmrdavid Mar 9, 2022
8d5d9bf
patch external contrib
davidmrdavid Mar 9, 2022
8351653
fix typo in test
davidmrdavid Mar 9, 2022
7232e58
comment changes
davidmrdavid Mar 14, 2022
d9b3baf
Adds IExternalInvoker (#776)
michaelpeng36 Mar 16, 2022
edf3011
rename hasOrchestrationContext to hasInitializedDurableFunction
davidmrdavid Mar 16, 2022
aca4c0b
remove outdated TODO comment
davidmrdavid Mar 16, 2022
e5ca2ba
remove now unused function - CreateOrchestrationBindingInfo
davidmrdavid Mar 16, 2022
f69b146
Allow worker to read results directly from the external SDK (#777)
michaelpeng36 Mar 17, 2022
4387799
comment out external SDK path
davidmrdavid Mar 17, 2022
a9a0a6e
change serialization
davidmrdavid Jul 12, 2022
12ccdee
Merge branch 'dev' of https://github.com/Azure/azure-functions-powers…
davidmrdavid Jul 12, 2022
5f862e6
restore orchestrationInvoker and powershellServices
davidmrdavid Jul 12, 2022
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
74 changes: 0 additions & 74 deletions src/Durable/PowerShellServices.cs

This file was deleted.

File renamed without changes.
36 changes: 36 additions & 0 deletions src/DurableSDK/Commands/GetDurableTaskResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

#pragma warning disable 1591 // Missing XML comment for publicly visible type or member 'member'

namespace Microsoft.Azure.Functions.PowerShellWorker.Durable.Commands
{
using System.Collections;
using System.Management.Automation;
using Microsoft.Azure.Functions.PowerShellWorker.Durable.Tasks;

[Cmdlet("Get", "DurableTaskResult")]
Copy link
Contributor Author

@davidmrdavid davidmrdavid Mar 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the community-contributed CmdLet that allows users to get the result of an already-completed Task. In other PLs, users can easily do this by re-await/re-yielding that Task, but the PS programming model doesn't have that. The implementation of this CmdLet isn't as efficient as it could be, but I don't think that's something we can't address after merging it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This addition seems to be independent from the main purpose of the PR. If this is not very difficult, I recommend separating this into its own PR. Unless this is indeed too difficult.

public class GetDurableTaskResultCommand : PSCmdlet
{
[Parameter(Mandatory = true)]
[ValidateNotNull]
public DurableTask[] Task { get; set; }

private readonly DurableTaskHandler _durableTaskHandler = new DurableTaskHandler();

protected override void EndProcessing()
{
var privateData = (Hashtable)MyInvocation.MyCommand.Module.PrivateData;
var context = (OrchestrationContext)privateData[SetFunctionInvocationContextCommand.ContextKey];

_durableTaskHandler.GetTaskResult(Task, context, WriteObject);
}

protected override void StopProcessing()
{
_durableTaskHandler.Stop();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,8 @@ protected override void EndProcessing()
{
var privateData = (Hashtable)MyInvocation.MyCommand.Module.PrivateData;
var context = (OrchestrationContext)privateData[SetFunctionInvocationContextCommand.ContextKey];
var loadedFunctions = FunctionLoader.GetLoadedFunctions();

var task = new ActivityInvocationTask(FunctionName, Input, RetryOptions);
ActivityInvocationTask.ValidateTask(task, loadedFunctions);
Comment on lines -46 to -49
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two were removed since they're incompatible with the external SDK. In addition, other DF SDKs don't perform this validation steps. If we want them, they ought to exist outside the DF SDKs.

I do agree that these are useful validations to have though, but if we perform these checks in the DF SDK components, then we're creating a strong dependency with worker-specific utilities.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent of ActivityInvocationTask.ValidateTask is to provide user-friendly error messages when the specified activity function does not exits (for example, because of a typo in the name). Without this code, the error messages are really confusing and unhelpful. If you remove it, we need an adequate replacement.


_durableTaskHandler.StopAndInitiateDurableTaskOrReplay(
task, context, NoWait.IsPresent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable.Commands
{
using System.Collections;
using System.Management.Automation;
using Microsoft.PowerShell.Commands;

/// <summary>
/// Set the orchestration context.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using System.Threading;
using Microsoft.Azure.Functions.PowerShellWorker.Durable.Tasks;
using Utility;
using Microsoft.PowerShell.Commands;

internal class DurableTaskHandler
{
Expand Down Expand Up @@ -47,6 +49,7 @@ public void StopAndInitiateDurableTaskOrReplay(
}

completedHistoryEvent.IsProcessed = true;
context.IsReplaying = completedHistoryEvent.IsPlayed;

switch (completedHistoryEvent.EventType)
{
Expand All @@ -57,6 +60,13 @@ public void StopAndInitiateDurableTaskOrReplay(
output(eventResult);
}
break;
case HistoryEventType.EventRaised:
var eventRaisedResult = GetEventResult(completedHistoryEvent);
if (eventRaisedResult != null)
{
output(eventRaisedResult);
}
break;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows WaitForExternalEvent tasks to output a result


case HistoryEventType.TaskFailed:
if (retryOptions == null)
Expand All @@ -76,7 +86,7 @@ public void StopAndInitiateDurableTaskOrReplay(
retryOptions.MaxNumberOfAttempts,
onSuccess:
result => {
output(TypeExtensions.ConvertFromJson(result));
output(ConvertFromJson(result));
},
onFailure);

Expand Down Expand Up @@ -126,6 +136,7 @@ public void WaitAll(
var allTasksCompleted = completedEvents.Count == tasksToWaitFor.Count;
if (allTasksCompleted)
{
context.IsReplaying = completedEvents.Count == 0 ? false : completedEvents[0].IsPlayed;
CurrentUtcDateTimeUpdater.UpdateCurrentUtcDateTime(context);

foreach (var completedHistoryEvent in completedEvents)
Expand Down Expand Up @@ -164,6 +175,7 @@ public void WaitAny(
if (scheduledHistoryEvent != null)
{
scheduledHistoryEvent.IsProcessed = true;
scheduledHistoryEvent.IsPlayed = true;
}

if (completedHistoryEvent != null)
Expand All @@ -179,12 +191,14 @@ public void WaitAny(
}

completedHistoryEvent.IsProcessed = true;
completedHistoryEvent.IsPlayed = true;
}
}

var anyTaskCompleted = completedTasks.Count > 0;
if (anyTaskCompleted)
{
context.IsReplaying = context.History[firstCompletedHistoryEventIndex].IsPlayed;
CurrentUtcDateTimeUpdater.UpdateCurrentUtcDateTime(context);
// Return a reference to the first completed task
output(firstCompletedTask);
Expand All @@ -195,6 +209,21 @@ public void WaitAny(
}
}

public void GetTaskResult(
IReadOnlyCollection<DurableTask> tasksToQueryResultFor,
OrchestrationContext context,
Action<object> output)
{
foreach (var task in tasksToQueryResultFor) {
var scheduledHistoryEvent = task.GetScheduledHistoryEvent(context, true);
var processedHistoryEvent = task.GetCompletedHistoryEvent(context, scheduledHistoryEvent, true);
if (processedHistoryEvent != null)
{
output(GetEventResult(processedHistoryEvent));
}
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of the community-contributed logic for "Get-TaskResult". Again, this could use some caching, but I'd rather optimize that later since this is feature is urgently missing.

public void Stop()
{
_waitForStop.Set();
Expand All @@ -206,15 +235,41 @@ private static object GetEventResult(HistoryEvent historyEvent)

if (historyEvent.EventType == HistoryEventType.TaskCompleted)
{
return TypeExtensions.ConvertFromJson(historyEvent.Result);
return ConvertFromJson(historyEvent.Result);
}
else if (historyEvent.EventType == HistoryEventType.EventRaised)
{
return TypeExtensions.ConvertFromJson(historyEvent.Input);
return ConvertFromJson(historyEvent.Input);
}
return null;
}

public static object ConvertFromJson(string json)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This used to exist in TypeExtensions, creating a tight-dependency with worker utilities. It's been copied here to allow this folder to be readily copied out into an external SDK

{
object retObj = JsonObject.ConvertFromJson(json, returnHashtable: true, error: out _);

if (retObj is PSObject psObj)
{
retObj = psObj.BaseObject;
}

if (retObj is Hashtable hashtable)
{
try
{
// ConvertFromJson returns case-sensitive Hashtable by design -- JSON may contain keys that only differ in case.
// We try casting the Hashtable to a case-insensitive one, but if that fails, we keep using the original one.
retObj = new Hashtable(hashtable, StringComparer.OrdinalIgnoreCase);
}
catch
{
retObj = hashtable;
}
}

return retObj;
}

private void InitiateAndWaitForStop(OrchestrationContext context)
{
context.OrchestrationActionCollector.Stop();
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
{
using System;
using System.Collections;
using System.Management.Automation;

internal interface IOrchestrationInvoker
{
Hashtable Invoke(OrchestrationBindingInfo orchestrationBindingInfo, IPowerShellServices pwsh);
void SetExternalInvoker(Action<PowerShell> externalInvoker);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,26 @@

namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
{
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
using System;
using System.Management.Automation;

internal interface IPowerShellServices
{
PowerShell GetPowerShell();

bool UseExternalDurableSDK();

void SetDurableClient(object durableClient);

void SetOrchestrationContext(OrchestrationContext orchestrationContext);
OrchestrationBindingInfo SetOrchestrationContext(ParameterBinding orchestrationContext, out Action<object> externalInvoker);

void ClearOrchestrationContext();

public void TracePipelineObject();
public void AddParameter(string name, object value);


IAsyncResult BeginInvoke(PSDataCollection<object> output);

void EndInvoke(IAsyncResult asyncResult);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
using System.Threading;

using Microsoft.Azure.Functions.PowerShellWorker.Durable.Actions;
using Newtonsoft.Json;

internal class OrchestrationActionCollector
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public class OrchestrationContext
public object Input { get; internal set; }

[DataMember]
internal string InstanceId { get; set; }
public string InstanceId { get; set; }

[DataMember]
internal string ParentInstanceId { get; set; }

[DataMember]
internal bool IsReplaying { get; set; }
public bool IsReplaying { get; set; }

[DataMember]
internal HistoryEvent[] History { get; set; }
Expand All @@ -35,6 +35,15 @@ public class OrchestrationContext

internal OrchestrationActionCollector OrchestrationActionCollector { get; } = new OrchestrationActionCollector();

internal object ExternalResult;
internal bool ExternalIsError;

internal void SetExternalResult(object result, bool isError)
{
this.ExternalResult = result;
this.ExternalIsError = isError;
}

internal object CustomStatus { get; set; }
}
}
Loading