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

Universal Wrapper #148

Merged
merged 13 commits into from
Jul 6, 2021
Merged

Universal Wrapper #148

merged 13 commits into from
Jul 6, 2021

Conversation

StefH
Copy link
Contributor

@StefH StefH commented Mar 7, 2021

No description provided.

@pamidur
Copy link
Owner

pamidur commented Mar 8, 2021

look good so far.
Do you think it is ok to mute the exceptions in our case?

@pamidur pamidur marked this pull request as draft March 8, 2021 15:09
@StefH
Copy link
Contributor Author

StefH commented Mar 8, 2021

look good so far.
Do you think it is ok to mute the exceptions in our case?

Maybe make this configurable?

@StefH
Copy link
Contributor Author

StefH commented Mar 9, 2021

@pamidur : I did move + fix some code and added a working example to the "Logging" console app.

@StefH StefH marked this pull request as ready for review March 9, 2021 20:23
Copy link
Owner

@pamidur pamidur left a comment

Choose a reason for hiding this comment

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

I love your PR! It can make the use of Aspect Injector even more convenient. I have added a few questions inline to discuss.

samples/Logging/AnyMethodHandleItAttribute.cs Outdated Show resolved Hide resolved
samples/src/Universal/Events/AspectEventArgs.cs Outdated Show resolved Hide resolved
samples/src/Universal/Events/AspectEventArgs.cs Outdated Show resolved Hide resolved
@StefH
Copy link
Contributor Author

StefH commented Mar 10, 2021

I love your PR! It can make the use of Aspect Injector even more convenient. I have added a few questions inline to discuss.

Thanks.

Please note that I've only created some Aspects and some attributes. This is just a start, more specific ones can still be added.

Is the name "AspectInjector.Universal" good and clear enough?

@pamidur
Copy link
Owner

pamidur commented Mar 10, 2021

I love your PR! It can make the use of Aspect Injector even more convenient. I have added a few questions inline to discuss.

Thanks.

Please note that I've only created some Aspects and some attributes. This is just a start, more specific ones can still be added.

Is the name "AspectInjector.Universal" good and clear enough?

I was thinking about it too. One option was 'Aspects.Framework' other was to make it part of AspectInjector package.
Maybe 'AspectInjector.Framework' in AspectInjector. sln?

For now let's keep it as is tho, I want to see how it goes. I feel like to make this framework really shine it might require some significant changes to the core and thus require bumping to 3.x

@pamidur
Copy link
Owner

pamidur commented Mar 11, 2021

One more thing to discuss.
All attributes instances are created in place of calling aspect now. Which means we instantiate attributes as many times as we call a target method. This is somewhat related to #137

The benefits of current approach: no side effects, data store in attribute is disposed the moment target method ends
The downside: performance

possible solution - cache attribute instances, but it is tricky (because generics) and we doing so we might as well cache all other things like name, type, and metadata.

@StefH
Copy link
Contributor Author

StefH commented Mar 11, 2021

One more thing to discuss.
All attributes instances are created in place of calling aspect now. Which means we instantiate attributes as many times as we call a target method. This is somewhat related to #137

The benefits of current approach: no side effects, data store in attribute is disposed the moment target method ends
The downside: performance

possible solution - cache attribute instances, but it is tricky (because generics) and we doing so we might as well cache all other things like name, type, and metadata.

Could this be solved at AspectInjector library level ?
Like : could this issue #147 also help on that ?

@pamidur
Copy link
Owner

pamidur commented Mar 11, 2021

One more thing to discuss.
All attributes instances are created in place of calling aspect now. Which means we instantiate attributes as many times as we call a target method. This is somewhat related to #137
The benefits of current approach: no side effects, data store in attribute is disposed the moment target method ends
The downside: performance
possible solution - cache attribute instances, but it is tricky (because generics) and we doing so we might as well cache all other things like name, type, and metadata.

Could this be solved at AspectInjector library level ?
Like : could this issue #147 also help on that ?

Yes, It can only be fixed on AspectInjector library level , I`m trying to evaluate if it should be fixed at all. #147 is definitely part of it

@sergeprozorov
Copy link

Hi guys,
This is a PR with a feature I am really looking forward to :)

The only note I would like to add, is it possible to re-work the wrapper in a way not to use MethodInfo.Invoke? It is slow as hell, comparing to a static or a virtual call. A good option would be using delegates, they are almost as fast as virtual ones.

I have this piece of code below, where I use delegates. Could you have a look, please?

internal static class UniversalWrapper
{
    private delegate object? DynamicWrapperDelegate(Func<object[], object> target, object[] args);

    private static readonly Dictionary<Type, DynamicWrapperDelegate> _delegateCache = new Dictionary<Type, DynamicWrapperDelegate>();

    private static readonly MethodInfo _asyncGenericHandler =
        typeof(UniversalWrapper).GetMethod(nameof(WrapAsync), BindingFlags.NonPublic | BindingFlags.Static);

    private static readonly Type _voidTaskResult = Type.GetType("System.Threading.Tasks.VoidTaskResult");

    public static object? Wrap(Func<object[], object> target, object[] args, Type returnType)
    {
        if (typeof(Task) == returnType) 
        {
            return WrapVoidAsync(target, args);
        }

        if (!typeof(Task).IsAssignableFrom(returnType))
        {
            // It is a sync call, no need to wrap anything around, just return the call result.
            return target(args);
        }

        return GetFromCache(returnType).Invoke(target, args);
    }

    private static async Task<T> WrapAsync<T>(Func<object[], object> target, object[] args)
    {
        return await ((Task<T>)target(args)).ConfigureAwait(false);
    }

    private static async Task WrapVoidAsync(Func<object[], object> target, object[] args)
    {
        await ((Task)target(args)).ConfigureAwait(false);
    }

    private static DynamicWrapperDelegate GetFromCache(Type returnType)
    {
        if (_delegateCache.ContainsKey(returnType))
        {
            return _delegateCache[returnType];
        }

        lock (_delegateCache)
        {
            if (_delegateCache.ContainsKey(returnType))
            {
                return _delegateCache[returnType];
            }

            var syncResultType = returnType.IsConstructedGenericType ? returnType.GenericTypeArguments[0] : _voidTaskResult;
            MethodInfo method = _asyncGenericHandler.MakeGenericMethod(syncResultType);

            var @delegate = (DynamicWrapperDelegate)Delegate.CreateDelegate(typeof(DynamicWrapperDelegate), method);
            _delegateCache.Add(returnType, @delegate);

            return @delegate;
        }
    }
}

@pamidur
Copy link
Owner

pamidur commented Mar 25, 2021

@sergeprozorov, I was actually thinking about something like this, but I though about constructing Expressions and compile them instead of dynamically creating delegates. Idk which one will be faster, On one hand compiling expression takes time, on the other hand delegate.Invoke isn't particularly fast either. It needs some testing.

Feel free to contribute!

oftopic: I glad to hear from you, it's been a long time since Exigen Services

@sergeprozorov
Copy link

Regarding Expression.Compile, it essentially produces a delegate, so it is a question of convenience and habits. But bear in mind, sometimes it doesn't work as fast as you might expect, Why is Func<> created from Expression<Func<>> slower than Func<> declared directly?
Regarding delegate .Invoke performance, it is quite close to direct calls. There are many benchmarks out there showing they are 10-20% slower, than direct calls. Whereas MethodInfo.Invoke is 600-1000 times slower than those.

And I am happy to hear from you too! Your framework is the real thing, very good work, mate, thanks!

@StefH
Copy link
Contributor Author

StefH commented Jun 13, 2021

@pamidur and @sergeprozorov
Dp you have any update on this PR?

@pamidur
Copy link
Owner

pamidur commented Jun 13, 2021

@pamidur and @sergeprozorov
Dp you have any update on this PR?

Hi, I'm ready to review and merge it. Just tell me when all the work on this one is done.

@StefH StefH changed the title Universal Wrapper (work in progress) Universal Wrapper Jun 17, 2021
@StefH
Copy link
Contributor Author

StefH commented Jun 17, 2021

@pamidur I think I have all the code ready, just review and comment if you want things changed.
(Or change it in my branch yourself)

@pamidur
Copy link
Owner

pamidur commented Jun 19, 2021

Hi @StefH , I have made quite a few changes:

  • made chaining two and more universal aspects more accurate
  • added caching
  • moved some logic to attributes

So I'd say it is pretty close now, we could merge and release it as a preview. Till full release will need:

wdyt?

Copy link
Contributor Author

@StefH StefH left a comment

Choose a reason for hiding this comment

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

some comments

@StefH
Copy link
Contributor Author

StefH commented Jun 20, 2021

@pamidur
Looks good.

I did add 3 small comments, however I'm not sure you can see these because I'm the author?

@StefH
Copy link
Contributor Author

StefH commented Jun 21, 2021

Looks good to me, I'll take this code and try it in my existing project. If that works fine, I think a preview version can be released on NuGet.

@pamidur pamidur mentioned this pull request Jul 6, 2021
@pamidur pamidur merged commit 5b809ec into pamidur:master Jul 6, 2021
@pamidur
Copy link
Owner

pamidur commented Jul 6, 2021

I have it merged @StefH , please continue your testing and then we'll work on releasing it

@StefH StefH deleted the stef_universalwrapper branch July 6, 2021 15:18
@StefH
Copy link
Contributor Author

StefH commented Jul 6, 2021

@pamidur
Can you please create a new preview from aspect-injector so that I can reference that one for the universal ?

@pamidur
Copy link
Owner

pamidur commented Jul 7, 2021

@StefH , you mean preview for aspect-injector or universal wrapper? If you mean aspect-injector there were little change since 2.6.0-preview so we can use it.

@StefH
Copy link
Contributor Author

StefH commented Jul 7, 2021

In this case "aspect-injector", to be sure all new code is there.

For "universal wrapper" : I'll just compile it in my project. No need for NuGet yet.

@pamidur
Copy link
Owner

pamidur commented Jul 7, 2021

It doesn't look like there were changes to aspect-injector itself since https://github.com/pamidur/aspect-injector/releases/tag/2.6.0-pre1 which I've merged into your branch before, so it should be fully compatible

@StefH
Copy link
Contributor Author

StefH commented Jul 16, 2021

I did use this code, and for my project it still works fine.

@pamidur
Copy link
Owner

pamidur commented Jul 20, 2021

Sounds like we can make a release then!

@StefH
Copy link
Contributor Author

StefH commented Aug 13, 2021

@pamidur
Did you have time yet to release this?

@pamidur
Copy link
Owner

pamidur commented Aug 13, 2021

Published here https://www.nuget.org/packages/Aspects.Universal/

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.

3 participants