Skip to content

Commit

Permalink
Merge pull request #1372 from Azure/dev
Browse files Browse the repository at this point in the history
Merge dev into master for v2.2.2 release
  • Loading branch information
Connor McMahon authored May 29, 2020
2 parents 88cc7f9 + 7018291 commit f51bbc0
Show file tree
Hide file tree
Showing 39 changed files with 1,142 additions and 576 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Generic;
using System.Linq;

Expand Down Expand Up @@ -45,7 +46,7 @@ public static void ReportProblems(
context.ReportDiagnostic(diagnostic);
}
}
else if (!SyntaxNodeUtils.InputMatchesOrCompatibleType(invocationInputType, definitionInputType))
else if (!IsValidArgumentForDefinition(invocationInputType, definitionInputType))
{
var invocationTypeName = SyntaxNodeUtils.GetQualifiedTypeName(invocationInputType);
var definitionTypeName = SyntaxNodeUtils.GetQualifiedTypeName(definitionInputType);
Expand All @@ -58,10 +59,22 @@ public static void ReportProblems(
}
}

private static bool IsValidArgumentForDefinition(ITypeSymbol invocationInputType, ITypeSymbol definitionInputType)
{
return SyntaxNodeUtils.InputMatchesOrCompatibleType(invocationInputType, definitionInputType)
|| SyntaxNodeUtils.TypeNodeImplementsOrExtendsType(invocationInputType, definitionInputType.ToString());
}

private static bool TryGetInvocationInputType(SemanticModel semanticModel, ActivityFunctionCall activityInvocation, out ITypeSymbol invocationInputType)
{
var invocationInput = activityInvocation.ParameterNode;

if (invocationInput == null)
{
invocationInputType = null;
return false;
}

invocationInputType = SyntaxNodeUtils.GetSyntaxTreeSemanticModel(semanticModel, invocationInput).GetTypeInfo(invocationInput).Type;

return invocationInputType != null;
Expand All @@ -71,6 +84,12 @@ private static bool TryGetDefinitionInputType(SemanticModel semanticModel, Activ
{
var definitionInput = functionDefinition.ParameterNode;

if (definitionInput == null)
{
definitionInputType = null;
return false;
}

if (FunctionParameterIsContext(semanticModel, definitionInput))
{
if (!TryGetInputFromDurableContextCall(semanticModel, definitionInput, out definitionInput))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Generic;
using System.Linq;

Expand Down Expand Up @@ -34,7 +35,7 @@ public static void ReportProblems(
TryGetInvocationReturnType(semanticModel, activityInvocation, out ITypeSymbol invocationReturnType);
TryGetDefinitionReturnType(semanticModel, functionDefinition, out ITypeSymbol definitionReturnType);

if (!InputMatchesOrTaskOrCompatibleType(invocationReturnType, definitionReturnType))
if (!IsValidReturnTypeForDefinition(invocationReturnType, definitionReturnType))
{
var invocationTypeName = SyntaxNodeUtils.GetQualifiedTypeName(invocationReturnType);
var functionTypeName = SyntaxNodeUtils.GetQualifiedTypeName(definitionReturnType);
Expand All @@ -47,22 +48,26 @@ public static void ReportProblems(
}
}

private static bool InputMatchesOrTaskOrCompatibleType(ITypeSymbol invocationReturnType, ITypeSymbol definitionReturnType)
private static bool IsValidReturnTypeForDefinition(ITypeSymbol invocationReturnType, ITypeSymbol definitionReturnType)
{
return SyntaxNodeUtils.InputMatchesOrCompatibleType(invocationReturnType, definitionReturnType) || DefinitionReturnsTask(invocationReturnType, definitionReturnType);
if (TryGetTaskTypeArgument(definitionReturnType, out ITypeSymbol taskTypeArgument))
{
definitionReturnType = taskTypeArgument;
}

return SyntaxNodeUtils.InputMatchesOrCompatibleType(invocationReturnType, definitionReturnType)
|| SyntaxNodeUtils.TypeNodeImplementsOrExtendsType(definitionReturnType, invocationReturnType.ToString());
}

private static bool DefinitionReturnsTask(ITypeSymbol invocationReturnType, ITypeSymbol definitionReturnType)
private static bool TryGetTaskTypeArgument(ITypeSymbol returnType, out ITypeSymbol taskTypeArgument)
{
if (definitionReturnType.Name.Equals("Task") && definitionReturnType is INamedTypeSymbol namedType)
if (returnType.Name.Equals("Task") && returnType is INamedTypeSymbol namedType)
{
var taskTypeArgument = namedType.TypeArguments.FirstOrDefault();
if (taskTypeArgument != null)
{
return invocationReturnType.Equals(taskTypeArgument);
}
taskTypeArgument = namedType.TypeArguments.FirstOrDefault();
return taskTypeArgument != null;
}

taskTypeArgument = null;
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ private static bool DiagnoseInvocations(List<MethodInformation> methodInformatio

foreach (MethodInformation method in methodInformationList)
{
var methodDeclaration = (MethodDeclarationSyntax)method.Declaration;

if (!method.IsDeterministic)
{
var invocation = method.Invocation;
Expand Down
61 changes: 50 additions & 11 deletions src/WebJobs.Extensions.DurableTask.Analyzers/SyntaxNodeUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,19 @@ internal static bool TryGetMethodReturnTypeNode(SyntaxNode node, out SyntaxNode
{
if (TryGetMethodDeclaration(node, out SyntaxNode methodDeclaration))
{
returnTypeNode = methodDeclaration.ChildNodes().Where(x => x.IsKind(SyntaxKind.GenericName) || x.IsKind(SyntaxKind.PredefinedType) || x.IsKind(SyntaxKind.IdentifierName) || x.IsKind(SyntaxKind.ArrayType)).FirstOrDefault();
return true;
return TryGetChildTypeNode(methodDeclaration, out returnTypeNode);
}

returnTypeNode = null;
return false;
}

private static bool TryGetChildTypeNode(SyntaxNode node, out SyntaxNode childTypeNode)
{
childTypeNode = node.ChildNodes().Where(x => x.IsKind(SyntaxKind.IdentifierName) || x.IsKind(SyntaxKind.PredefinedType) || x.IsKind(SyntaxKind.GenericName) || x.IsKind(SyntaxKind.ArrayType) || x.IsKind(SyntaxKind.TupleType)).FirstOrDefault();
return childTypeNode != null;
}

private static bool TryGetAttribute(SyntaxNode node, string attributeName, out SyntaxNode attribute)
{
if (TryGetMethodDeclaration(node, out SyntaxNode methodDeclaration))
Expand Down Expand Up @@ -175,7 +180,8 @@ public static bool TryParseFunctionName(SemanticModel semanticModel, SyntaxNode
return true;
}

if (TryGetFunctionNameInConstant(semanticModel, node, out functionName))
var newSemanticModel = GetSyntaxTreeSemanticModel(semanticModel, node);
if (TryGetFunctionNameInConstant(newSemanticModel, node, out functionName))
{
return true;
}
Expand Down Expand Up @@ -268,8 +274,7 @@ private static bool TryGetFunctionNameAttributeArgument(SyntaxNode functionAttri
internal static bool TryGetParameterNodeNextToAttribute(SyntaxNodeAnalysisContext context, AttributeSyntax attributeExpression, out SyntaxNode inputType)
{
var parameter = attributeExpression.Parent.Parent;
inputType = parameter.ChildNodes().Where(x => x.IsKind(SyntaxKind.IdentifierName) || x.IsKind(SyntaxKind.PredefinedType) || x.IsKind(SyntaxKind.GenericName)).FirstOrDefault();
return inputType != null;
return TryGetChildTypeNode(parameter, out inputType);
}

internal static bool TryGetTypeArgumentNode(MemberAccessExpressionSyntax expression, out SyntaxNode identifierNode)
Expand Down Expand Up @@ -312,7 +317,7 @@ internal static string GetQualifiedTypeName(ITypeSymbol typeInfo)
var tupleUnderlyingType = namedTypeInfo.TupleUnderlyingType;
if (tupleUnderlyingType != null)
{
return $"System.Tuple<{string.Join(", ", tupleUnderlyingType.TypeArguments.Select(x => x.ToString()))}>";
return $"({string.Join(", ", tupleUnderlyingType.TypeArguments.Select(x => x.ToString()))})";
}

return typeInfo.ToString();
Expand Down Expand Up @@ -342,11 +347,11 @@ internal static bool InputMatchesOrCompatibleType(ITypeSymbol invocationType, IT
}

return invocationType.Equals(definitionType)
|| AreEqualTupleTypes(invocationType, definitionType)
|| AreCompatibleIEnumerableTypes(invocationType, definitionType);
|| AreCompatibleIEnumerableTypes(invocationType, definitionType)
|| AreEqualQualifiedTypeNames(invocationType, definitionType);
}

private static bool AreEqualTupleTypes(ITypeSymbol invocationType, ITypeSymbol definitionType)
private static bool AreEqualQualifiedTypeNames(ITypeSymbol invocationType, ITypeSymbol definitionType)
{
var invocationQualifiedName = GetQualifiedTypeName(invocationType);
var definitionQualifiedName = GetQualifiedTypeName(definitionType);
Expand All @@ -358,11 +363,45 @@ private static bool AreCompatibleIEnumerableTypes(ITypeSymbol invocationType, IT
{
if (AreArrayOrNamedTypes(invocationType, functionType) && UnderlyingTypesMatch(invocationType, functionType))
{
return ((invocationType.AllInterfaces.Any(i => i.Name.Equals("IEnumerable")))
&& (functionType.AllInterfaces.Any(i => i.Name.Equals("IEnumerable"))));
return TypeNodeImplementsOrExtendsType(invocationType, "IEnumerable")
&& TypeNodeImplementsOrExtendsType(functionType, "IEnumerable");
}

return false;
}

public static bool TypeNodeImplementsOrExtendsType(ITypeSymbol node, string interfaceOrBase)
{
if (string.IsNullOrEmpty(interfaceOrBase))
{
return false;
}

return node.AllInterfaces.Any(i => i.Name.Equals(interfaceOrBase))
|| TypeNodeIsSubclass(node, interfaceOrBase);

}

private static bool TypeNodeIsSubclass(ITypeSymbol node, string baseClass)
{
if (node == null || string.IsNullOrEmpty(baseClass))
{
return false;
}

var curr = node.BaseType;
while (curr != null)
{
if (curr.ToString().Equals(baseClass))
{
return true;
}

curr = curr.BaseType;
}

return false;

}

private static bool AreArrayOrNamedTypes(ITypeSymbol invocationType, ITypeSymbol functionType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<PropertyGroup>
<PackageId>Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers</PackageId>
<PackageVersion>0.2.1</PackageVersion>
<PackageVersion>0.2.3</PackageVersion>
<Authors>Microsoft</Authors>
<PackageLicenseUrl>https://go.microsoft.com/fwlink/?linkid=2028464</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/Azure/azure-functions-durable-extension</PackageProjectUrl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal class AzureStorageDurabilityProviderFactory : IDurabilityProviderFactor
private readonly AzureStorageOptions azureStorageOptions;
private readonly IConnectionStringResolver connectionStringResolver;
private readonly string defaultConnectionName;
private readonly INameResolver nameResolver;
private AzureStorageDurabilityProvider defaultStorageProvider;

// Must wait to get settings until we have validated taskhub name.
Expand All @@ -22,9 +23,11 @@ internal class AzureStorageDurabilityProviderFactory : IDurabilityProviderFactor

public AzureStorageDurabilityProviderFactory(
IOptions<DurableTaskOptions> options,
IConnectionStringResolver connectionStringResolver)
IConnectionStringResolver connectionStringResolver,
INameResolver nameResolver)
{
this.options = options.Value;
this.nameResolver = nameResolver;
this.azureStorageOptions = new AzureStorageOptions();
JsonConvert.PopulateObject(JsonConvert.SerializeObject(this.options.StorageProvider), this.azureStorageOptions);

Expand Down Expand Up @@ -143,6 +146,15 @@ internal AzureStorageOrchestrationServiceSettings GetAzureStorageOrchestrationSe
ThrowExceptionOnInvalidDedupeStatus = true,
};

// When running on App Service VMSS stamps, these environment variables are the best way
// to enure unqique worker names
string stamp = this.nameResolver.Resolve("WEBSITE_CURRENT_STAMPNAME");
string roleInstance = this.nameResolver.Resolve("RoleInstanceId");
if (!string.IsNullOrEmpty(stamp) && !string.IsNullOrEmpty(roleInstance))
{
settings.WorkerId = $"{stamp}:{roleInstance}";
}

if (!string.IsNullOrEmpty(this.azureStorageOptions.TrackingStoreNamePrefix))
{
settings.TrackingStoreNamePrefix = this.azureStorageOptions.TrackingStoreNamePrefix;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ internal DurableCommonContext(DurableTaskExtension config, string functionName)

internal TimeSpan EntityMessageReorderWindow { get; private set; }

internal bool ExecutorCalledBack { get; set; }

internal void AddDeferredTask(Func<Task> function)
{
this.deferredTasks.Add(function);
Expand Down
30 changes: 29 additions & 1 deletion src/WebJobs.Extensions.DurableTask/DurableTaskExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ private void InitializeForFunctionsV1(ExtensionConfigContext context)
ILogger logger = context.Config.LoggerFactory.CreateLogger(LoggerCategoryName);
this.TraceHelper = new EndToEndTraceHelper(logger, this.Options.Tracing.TraceReplayEvents);
this.connectionStringResolver = new WebJobsConnectionStringProvider();
this.durabilityProviderFactory = new AzureStorageDurabilityProviderFactory(new OptionsWrapper<DurableTaskOptions>(this.Options), this.connectionStringResolver);
this.durabilityProviderFactory = new AzureStorageDurabilityProviderFactory(new OptionsWrapper<DurableTaskOptions>(this.Options), this.connectionStringResolver, this.nameResolver);
this.defaultDurabilityProvider = this.durabilityProviderFactory.GetDurabilityProvider();
this.LifeCycleNotificationHelper = this.CreateLifeCycleNotificationHelper();
var messageSerializerSettingsFactory = new MessageSerializerSettingsFactory();
Expand Down Expand Up @@ -512,6 +512,8 @@ private async Task OrchestrationMiddleware(DispatchMiddlewareContext dispatchCon
#pragma warning disable CS0618 // Approved for use by this extension
InvokeHandler = async userCodeInvoker =>
{
context.ExecutorCalledBack = true;

// 2. Configure the shim with the inner invoker to execute the user code.
shim.SetFunctionInvocationCallback(userCodeInvoker);

Expand All @@ -526,6 +528,7 @@ private async Task OrchestrationMiddleware(DispatchMiddlewareContext dispatchCon
},
#pragma warning restore CS0618
},
context,
this.hostLifetimeService.OnStopping);

if (result.ExecutionStatus == WrappedFunctionResult.FunctionResultStatus.FunctionsRuntimeError)
Expand Down Expand Up @@ -688,6 +691,8 @@ private async Task EntityMiddleware(DispatchMiddlewareContext dispatchContext, F
#pragma warning disable CS0618 // Approved for use by this extension
InvokeHandler = async userCodeInvoker =>
{
entityContext.ExecutorCalledBack = true;

entityShim.SetFunctionInvocationCallback(userCodeInvoker);

// 3. Run all the operations in the batch
Expand All @@ -713,6 +718,7 @@ private async Task EntityMiddleware(DispatchMiddlewareContext dispatchContext, F
},
#pragma warning restore CS0618
},
entityContext,
this.hostLifetimeService.OnStopping);

if (result.ExecutionStatus == WrappedFunctionResult.FunctionResultStatus.FunctionsRuntimeError)
Expand Down Expand Up @@ -1108,5 +1114,27 @@ internal static string ValidatePayloadSize(string payload)

return payload;
}

#if !FUNCTIONS_V1
/// <summary>
/// Tags the current Activity with metadata: DurableFunctionsType, DurableFunctionsInstanceId, DurableFunctionsRuntimeStatus.
/// This metadata will show up in Application Insights, if enabled.
/// </summary>
internal static void TagActivityWithOrchestrationStatus(OrchestrationRuntimeStatus status, string instanceId, bool isEntity = false)
{
// Adding "Tags" to activity allows using Application Insights to query current state of orchestrations
Activity activity = Activity.Current;
string functionsType = isEntity ? "Entity" : "Orchestrator";

// The activity may be null when running unit tests, but should be non-null otherwise
if (activity != null)
{
string statusStr = Enum.GetName(status.GetType(), status);
activity.AddTag("DurableFunctionsType", functionsType);
activity.AddTag("DurableFunctionsInstanceId", instanceId);
activity.AddTag("DurableFunctionsRuntimeStatus", statusStr);
}
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,28 @@ internal EntityContextProxy(IDurableEntityContext context, DateTime scheduledTim

public Task CallAsync(EntityId entityId, string operationName, object operationInput)
{
this.context.SignalEntity(entityId, operationName, operationInput);
if (this.scheduledTimeForSignal.HasValue)
{
this.context.SignalEntity(entityId, this.scheduledTimeForSignal.Value, operationName, operationInput);
}
else
{
this.context.SignalEntity(entityId, operationName, operationInput);
}

return Task.CompletedTask;
}

public Task<TResult> CallAsync<TResult>(EntityId entityId, string operationName, object operationInput)
{
this.context.SignalEntity(entityId, operationName, operationInput);
if (this.scheduledTimeForSignal.HasValue)
{
this.context.SignalEntity(entityId, this.scheduledTimeForSignal.Value, operationName, operationInput);
}
else
{
this.context.SignalEntity(entityId, operationName, operationInput);
}

return Task.FromResult(default(TResult));
}
Expand Down
Loading

0 comments on commit f51bbc0

Please sign in to comment.