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

Getting batched events #205

Merged
merged 24 commits into from
Mar 5, 2021
Merged

Getting batched events #205

merged 24 commits into from
Mar 5, 2021

Conversation

mhoeger
Copy link
Contributor

@mhoeger mhoeger commented Mar 2, 2021

Resolves #127
Resolves #115

SDK Changes

  • For inputs, try to set "dataType" if we know the requested payload is string or byte[]
  • For trigger attributes that implement IBatchedOutput, convert IsBatched property to add "cardinality": "many" or "cardinality": "one".
    • Fail if we detect "cardinality": "many" but the requested payload DOES NOT implement IEnumerable
    • Set "dataType" if we know that the requested payload inside of the collection is string or byte[]
  • Note that not all IEnumerables will be converted properly by .NET worker side (example: "Queue"). SDK shouldn't need to be changed though for future converter additions.

.NET Worker Changes

  • Enable capability to receive TypedData of:
    • collection bytes
    • collection string
    • collection double
    • collection long
  • New converter which converts RepeatedCollection<string>, RepeatedCollection<byte[]>, RepeatedCollection<ReadOnlyMemory<byte>>, RepeatedCollection<double>, and RepeatedCollection<long> (from above capability) to corresponding Array types.
    • RepeatedCollection implements IEnumerable, IList, and ICollection

E2E App Changes

  • Added some EventHubs functions I was using to test - not hooked up to actual tests yet since we can't emulate and the PR is already big...

@fabiocav
Copy link
Member

fabiocav commented Mar 3, 2021

Was this replaced by the other PR? Should it be closed?

@mhoeger mhoeger force-pushed the mhoeger/cardinalityManyChange branch from 7900f7d to 5c6d9d7 Compare March 4, 2021 04:13
@mhoeger mhoeger changed the title "Cardinality": "Many" and getting collections Getting batched events Mar 4, 2021
Copy link
Member

@fabiocav fabiocav left a comment

Choose a reason for hiding this comment

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

This looks great! Awesome set of tests.

Leaving an initial set of comments, but some things may require a chat

extensions/Worker.Extensions.Abstractions/IBatchedInput.cs Outdated Show resolved Hide resolved
sdk/Sdk/DataTypeEnum.cs Outdated Show resolved Hide resolved
sdk/Sdk/FunctionMetadataGenerator.cs Outdated Show resolved Hide resolved
sdk/Sdk/FunctionMetadataGenerator.cs Show resolved Hide resolved
src/DotNetWorker/Converters/EnumerableConverter.cs Outdated Show resolved Hide resolved
src/DotNetWorker/Converters/EnumerableConverter.cs Outdated Show resolved Hide resolved
namespace Microsoft.Azure.Functions.Worker.Converters
{
// Converting IEnumerable<> to Array
internal class EnumerableConverter : IConverter
Copy link
Member

Choose a reason for hiding this comment

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

Adding some suggestions to the class, but given the fact this is pretty specific to gRPC, I'm wondering if this shouldn't be a converted injected by the gRPC integration (mostly thinking about when we break it off) and operating on the gRPC types, which would make this a bit easier and rely less on type checking. We can chat, but wanted to share this to get your thoughts.

sdk/Sdk/FunctionMetadataGenerator.cs Outdated Show resolved Hide resolved
sdk/Sdk/FunctionMetadataGenerator.cs Outdated Show resolved Hide resolved
sdk/Sdk/FunctionMetadataGenerator.cs Outdated Show resolved Hide resolved
EnumerableTargetType? targetType = null;
target = null;
// Array
if (context.Parameter.Type.IsArray)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Use ElementType or GetElementType API

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Add cache on function id and parameter (or add a TODO)

…s collections

event hubs e2e initial

updates

temp

add to solution too

update to new model

rebase to latest

Improved logic for resolving generics

remove webjobs reference

Fix IEnumerable

some enumerable converter tests

Clean up test

Cleanup

reverting local settings json change

change localsettings

typo

added datatype string to some tests

fix generator test
@mhoeger mhoeger force-pushed the mhoeger/cardinalityManyChange branch from 2f627df to 8d0d9a1 Compare March 5, 2021 02:25
namespace Microsoft.Azure.Functions.Worker
{
[AttributeUsage(AttributeTargets.Property)]
public class DefaultValueAttribute : Attribute
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 is temporary to set default while we do not load attributes.

Copy link
Contributor

Choose a reason for hiding this comment

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

nit:No need to change for right now, but I wonder if we should move this to a different namespace so it's not discoverable and polluting?

LoadConstructorArguments(properties, attribute);
LoadDefinedProperties(properties, attribute);

return properties;
}

private static void LoadConstructorArguments(IDictionary<string, object> properties, CustomAttribute attribute)
private static IEnumerable<(string, CustomAttributeArgument?)> GetDefaultValues(this CustomAttribute attribute)
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 is all should be removed later... after above todo is done: avoid needing to instantiate any types, assume that the constructor argument names are equal to property names.

// Note that this logic doesn't dictate what we can/can't do, and
// we can be more restrictive in the future because today some
// scenarios result in runtime failures.
if (IsIterableCollection(parameterType, out DataType dataType))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that this should be changed, but likely for a future iteration it will be a runtime failure anyways, so no worries about introducing breaking behavior (it'll just fail faster)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually - something that is derived from IEnumerable<Poco> (ex: List<Poco>) is valid for cardinality: many and does have an appropriate converter. Would not want to throw in these cases.

The only failure cases are when element type is string, byte[], ReadOnlyMemory<byte> int, double, long AND collection type is not equal to IEnumerable, IList, or ICollection (or corresponding generic).

We could go one step further and say if not derived from IEnumerable assume it's not batched... in this case we want to know if it was set explicitly or if it's just from the user... for event hubs we definitely want to discourage non-batched anyways

|| elementType.IsAssignableFrom(typeof(byte[]))
|| elementType.IsAssignableFrom(typeof(ReadOnlyMemory<byte>))
|| elementType.IsAssignableFrom(typeof(long))
|| elementType.IsAssignableFrom(typeof(double)))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: should improve efficiency here... if anyone has quick suggestions they would be very welcome but otherwise i think i'll flag this as a future improvement

Copy link
Member

@fabiocav fabiocav left a comment

Choose a reason for hiding this comment

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

Added some comments, but nothing major. We should be able to get this in as I don't see major blockers, but some of the comments would be good to address.

}

// IEnumerable and not string or dictionary
bool isEnumerableOfT = IsOrDerivedFrom(type, Constants.IEnumerableOfT);
Copy link
Member

Choose a reason for hiding this comment

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

nit: Since this may not be used until you reach the last check, could we defer this check?

Copy link
Contributor Author

@mhoeger mhoeger Mar 5, 2021

Choose a reason for hiding this comment

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

is there a fancy .net thing i can do to assign while being evaluated (like if (bool t = ShouldContinue())? I use isEnumerableOfT a bit earlier in line 473 too

src/DotNetWorker/Converters/ArrayConverter.cs Outdated Show resolved Hide resolved
src/DotNetWorker/Converters/ArrayConverter.cs Outdated Show resolved Hide resolved
src/DotNetWorker/Converters/ArrayConverter.cs Show resolved Hide resolved
src/DotNetWorker/Converters/ArrayConverter.cs Outdated Show resolved Hide resolved
src/DotNetWorker/Converters/ArrayConverter.cs Outdated Show resolved Hide resolved
test/DotNetWorkerTests/Converters/ArrayConverterTests.cs Outdated Show resolved Hide resolved
namespace Microsoft.Azure.Functions.Worker
{
[AttributeUsage(AttributeTargets.Property)]
public class DefaultValueAttribute : Attribute
Copy link
Contributor

Choose a reason for hiding this comment

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

nit:No need to change for right now, but I wonder if we should move this to a different namespace so it's not discoverable and polluting?

sdk/Sdk/CustomAttributeExtensions.cs Outdated Show resolved Hide resolved
@mhoeger
Copy link
Contributor Author

mhoeger commented Mar 5, 2021

Looks like long[] and double[] aren't used by host - updated release notes to reflect this and filed issue: Azure/azure-functions-host#7209

Copy link
Contributor

@ankitkumarr ankitkumarr left a comment

Choose a reason for hiding this comment

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

Looks great! All these tests make it so much easier. Thank you!!

I added very minor final nit comments, that don't need to be addressed in this PR. The only thing I will leave it to you is to change the namespace Microsoft.Azure.Functions.Worker.Annotations

return IsDerivedFrom(baseType, interfaceFullName);
}

private static string? ResolveIEnumerableOfTType(TypeReference type, Dictionary<string, string> foundMapping)
Copy link
Contributor

Choose a reason for hiding this comment

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

For later -- If I am reading it right, this finds IEnumerable<T> through recursion, while IsOrDerivedFrom(..., Constants.IEnumerableOfT checks if there's any. They both seem to go through all interfaces directly or indirectly implemented through baseType. Is there a reason to do both? Could you combine the two to be like TryGetIEnumerableOfTType? That way we don't have to recurse the same path twice?

I understand that you may still want IsOrDerivedFrom to be around for other IEnumerable types.

Definitely not required right now. Was just curious and wanted to make sure I am understanding it right.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely a good call!!

Copy link
Member

Choose a reason for hiding this comment

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

We should flag some of the logic using mono cecil for review. I noticed a few other things we may want to reevaluate, but these are much simpler to walk through with the tests.

TypeDefinition definition = type.Resolve();
if (definition.HasGenericParameters && type is GenericInstanceType genericType)
{
for (int i = 0; i < genericType.GenericArguments.Count(); i++)
Copy link
Contributor

Choose a reason for hiding this comment

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

For later -- Would it ever make sense to just look at the first argument, as we know IEnumerable<T> would always only have at most one generic arg?

Also this can be made foreach. As apparently, ElementAt() could go over the whole ienumerable every lookup depending on the implementation it says. Not that it matters much here :)

Copy link
Contributor Author

@mhoeger mhoeger Mar 5, 2021

Choose a reason for hiding this comment

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

^This was done because you might have some Foo<K, V> : IEnumerable<V>

So in this case the recursive search would want to match V from Foo (the second generic) with V from IEnumerable (the first and only generic)

Copy link
Member

Choose a reason for hiding this comment

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

Let's follow up on this case.

sdk/Sdk/CustomAttributeExtensions.cs Outdated Show resolved Hide resolved
@mhoeger mhoeger merged commit 16856c4 into main Mar 5, 2021
@mhoeger mhoeger deleted the mhoeger/cardinalityManyChange branch March 5, 2021 07:55

private static object? GetBinaryData(IEnumerable<ReadOnlyMemory<byte>> source, Type targetType)
{
if (targetType.IsAssignableFrom(typeof(ReadOnlyMemory<byte>)))
Copy link
Member

Choose a reason for hiding this comment

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

Had left an earlier comment about the comparison using IsAssignableFrom, but can't find it. This check here isn't required, are any of the tests specifically testing this code path? Aside from ReadOnlyMemory<byte> this shouldn't evaluate to true, in practice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for batch/cardinality Support array types as input data (string[], byte[][], long[], double[])
3 participants