Comments (33)
I forgot about a commitment I have tonight. I'm almost done with the change, though. Tomorrow night for sure.
from methodboundaryaspect.fody.
Hi, nice idea.
We accept PR ;-)
from methodboundaryaspect.fody.
If I understand correctly, OP wants it to work something like this (untested):
class Aspect : OnMethodBoundaryAspect {
public override void OnEntry(MethodExecutionArgs args) {
args.FlowBehavior = FlowBehavior.Return;
args.ReturnValue = "passed";
}
}
class TestClass {
[Aspect]public static string Method() => throw new Exception("failed");
}
class Tests {
[Fact] public void Test() {
Assert.Equal("passed", TestClass.Method());
}
}
It would only work in OnEntry
and should probably trigger the same behavior as FlowBehavior.Continue
when used after OnException
. This is not difficult to do.
However, there's an edge case to consider. Consider the case where multiple aspects, each with an OnEntry
handler, all apply to the same method. PostSharp's flow behavior here is a little subtle. If an aspect's OnEntry
sets flow behavior to Return
, then it will indeed return before running any further aspects, but first it will trigger the OnExit
of any aspects which already ran their OnEntry
s. See http://doc.postsharp.net/t_postsharp_aspects_flowbehavior for the docs.
The idea of PostSharp's implementation is to mimic the behavior of an interceptor: if you don't allow flow to continue past your OnEntry
, then deeper layers (including the unwoven method) will not be executed but any aspects at a higher layer than you (whose entry happened before yours) will still get their OnExit
. The idea is to allow any aspects which already began their executions to clean up any resources they claimed.
I assume, based on past interactions, that @Ralf1108 and @marcells would want the behavior of MBA.Fody to mimic PostSharp as much as possible, but one of them should probably confirm on this subject. Would also be nice to hear from OP about this.
I can probably work on this over the weekend, but I wanted to point all this out in case someone beats me to it.
from methodboundaryaspect.fody.
You are correct. Mimicing the behaviour of post sharp users have it easier to transition to MBA and learned stuff from post sharp can be reused
from methodboundaryaspect.fody.
This is what I'd like to achive. I'm not sure if this is the correct way to do it.
public class CacheThisMethod : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
if (isCached( args.Method.name)
{
args.MethodExecutionTag = getReturnValue(args.Method.name)
OnExit(args);
}
else
{
//continue
}
}
public override void OnExit(MethodExecutionArgs args)
{
args.ReturnValue = args.MethodExecutionTag;
args.FlowBehavior = FlowBehavior.Return;
}
}
Note that this is not the complete code, it just shows the basic idea.
from methodboundaryaspect.fody.
Ok. I'm pretty much done with the Return
flow behavior when set from OnEntry
. However, I don't know what to do for the OnException
case. That is, if I have two aspects handling OnException
, and the first one changes FlowBehavior
to Return
(or Continue
for that matter), should the second one get its OnException
handler called or its OnExit
or neither? I don't know what PostSharp's behavior is in this case. (and note that MBA's OnExit
is analogous to PostSharp's OnSuccess
, not PostSharp's OnExit
) What should MBA do? As soon as I have an idea what to do with that, I can probably finish this enhancement. The latest version will only check FlowBehavior
after the last aspect implementing OnException
is called, not in between each one as PostSharp seems to do.
I also just thought of another issue on this feature. Consider the following:
class Aspect : OnMethodBoundaryAspect {
public override void OnEntry(MethodExecutionArgs args) {
args.FlowBehavior = FlowBehavior.Return;
args.ReturnValue = 42;
}
}
class Program {
private static int _field = 0;
[Aspect]
public static ref int GetInt() => ref m_field;
}
As is, the method will get transformed to this:
public static ref int GetInt() {
var aspect = new Aspect();
var args = new MethodExecutionArgs();
// various setup
aspect.OnEntry(args);
if (args.FlowBehavior == FlowBehavior.Return) {
int returnValue = args.ReturnValue;
return ref returnValue;
}
return ref m_field;
}
But that's illegal: you cannot return a byref variable that refers to a local inside your own method. I don't know whether the JITter would throw an intelligent exception or just crash, but it won't be desired behavior either way. Perhaps for ref return types it should not check for FlowBehavior
after OnEntry
. Or perhaps it should be weaved to this instead:
public static ref int GetInt() {
var aspect = new Aspect();
var args = new MethodExecutionArgs();
// various setup
aspect.OnEntry(args);
if (args.FlowBehavior == FlowBehavior.Return) {
throw new InvalidOperationException("FlowBehavior.Return is not supported in OnEntry when return type is ByReference.");
}
return ref m_field;
}
Again, I have no idea how PostSharp handles it. I'll hold on making any more progress until I hear back on these issues.
from methodboundaryaspect.fody.
If there is no other recommendation from @KleaTech regarding the expected behavior I think we should keep it for now like you implemented it. Maybe later during usage of this feature there will be more insights what is the correct behavior with specific use cases.
Regarding the "ref return".
For now we should throw just an NotSupportedException. In my opinion the only scenarios where the "ref return" will be used are for low level high performance code and because of this there wont be any aspect woven into. Also here we wait for a use case before putting more effort in it -> YAGNI ;-)
from methodboundaryaspect.fody.
I don't have enough experience to tell what is the expected behaviors in the special cases. But since you asked, here's my opinion. I think the "return ref" case is not that important, so the NotSuppertedException is fine for now.
Regarding the OnException case, I don't know how PostScript handles it, but I think this would be a sufficient behavior: if you set FlowBehavior to Return in any hook (OnEntry, OnException or OnExit), the method should return immediately after that hook. So no other hook would be triggered after that. If there are more aspects, than their order will define which one will run and which won't. If there is an aspect with FlowBehavior set to Return in one of it's hooks, the aspects after that won't run.
Also I can't see a reason not to allow FlowBehavior.Return in the OnExit hook, so please make it possible to use it there. So my example would work. Below is a more real example which - according to a StackOverflow answer - should work with PostScript.
public sealed class CacheAttribute : OnMethodBoundaryAspect
{
public static DictionaryCache Cache { get; set; } = new DictionaryCache();
public override void OnEntry(MethodExecutionArgs args)
{
var method = args.Method;
var cacheKey = method.DeclaringType.FullName + method.Name + '_' + String.Join("_", args.Arguments);
if (Cache.Contains(cacheKey))
{
args.MethodExecutionTag = Cache.Retrieve<object>(cacheKey);
OnExit(args);
}
}
public override void OnExit(MethodExecutionArgs args)
{
var method = args.Method;
var cacheKey = method.DeclaringType.FullName + method.Name + '_' + String.Join("_", args.Arguments);
if (Cache.Contains(cacheKey))
{
args.ReturnValue = args.MethodExecutionTag;
args.FlowBehavior = FlowBehavior.Return;
}
else
Cache.Store(cacheKey, args.ReturnValue);
}
public override void OnException(MethodExecutionArgs args)
{
}
}
from methodboundaryaspect.fody.
To make OnExit
respect FlowBehavior.Return
would definitely contradict PostSharp's behavior. @Ralf1108 what do you say?
from methodboundaryaspect.fody.
@KleaTech
would this implementation not simplify your use case?
If method is called and a cached value exists -> return
If method is called and a no cached value exists -> method is invoked and return value is added to cache. Next method call will use the cached value.
public sealed class CacheAttribute : OnMethodBoundaryAspect
{
public static DictionaryCache Cache { get; set; } = new DictionaryCache();
public override void OnEntry(MethodExecutionArgs args)
{
var method = args.Method;
var cacheKey = method.DeclaringType.FullName + method.Name + '_' + String.Join("_", args.Arguments);
if (Cache.Contains(cacheKey))
{
args.ReturnValue = Cache.Retrieve<object>(cacheKey);
args.FlowBehavior = FlowBehavior.Return;
}
}
public override void OnExit(MethodExecutionArgs args)
{
var method = args.Method;
var cacheKey = method.DeclaringType.FullName + method.Name + '_' + String.Join("_", args.Arguments);
Cache.Store(cacheKey, args.ReturnValue);
}
}
from methodboundaryaspect.fody.
Thinking about the whole feature and discussing it with a colleage:
What @KleaTech really want is a "MethodInterceptionAspect". This also exists in PostSharp.
Our "MethodBoundaryAspect" is more specialized for adding code which is executed everytime.
So for implementing the "return flow behavior" I think the OnEntry and OnExit on all aspects should be callled to not introduce strange and difficult to debug side effects.
If a aspect mind to dont do anything if a return value was set then it can skip execution explicitly. But the default expectation from the aspect is that all OnXXX methods are called appropriately.
That would mean for the implementation:
- check return flow only after OnXXX methods of all aspects were called
- after calling one OnXXX method we have to transfer the "ReturnValue" and "FlowBehavior" value from the last used MethodExecutionArgs to the current one, so the next aspect can use this information.
To 100% match @KleaTech requirements we should implement a MethodInterceptionAspect. Hopefully there could be many code from this aspect be reused.
With this you can control if and how often a method should be called, either for using cached values or implement a retry behavior.
Any thoughts? :-)
from methodboundaryaspect.fody.
So, @Ralf1108 , are you saying that OnExit
should not be called even if FlowBehavior.Return
is used in OnEntry
, despite that being the way PostSharp does it? For example, this...
[Aspect1, Aspect2, Aspect3]public static string GetMessage() {
return "Hello, World!";
}
gets weaved to this in PostSharp...
public static string GetMessage() {
var aspect1 = new Aspect1();
var aspect2 = new Aspect2();
var aspect3 = new Aspect3();
var args = new MethodExecutionArgs();
// various setup
aspect1.OnEntry(args);
var tag1 = args.MethodExecutionTag;
if (args.FlowBehavior == FlowBehavior.Return)
return (string)args.ReturnValue;
aspect2.OnEntry(args);
var tag2 = args.MethodExecutionTag;
if (args.FlowBehavior == FlowBehavior.Return) {
args.MethodExecutionTag = tag1;
aspect1.OnExit(args);
return (string)args.ReturnValue;
}
aspect3.OnEntry(args);
var tag3 = args.MethodExecutionTag;
if (args.FlowBehavior == FlowBehavior.Return) {
args.MethodExecutionTag = tag2;
aspect2.OnExit(args);
args.MethodExecutionTag = tag1;
aspect1.OnExit(args);
}
string returnValue = null;
try {
returnValue = "Hello, World!";
}
catch (Exception ex) {
args.Exception = ex;
args.MethodExecutionTag = tag3;
aspect3.OnException(args);
if (args.FlowBehavior == FlowBehavior.Return || args.FlowBehavior == FlowBehavior.Continue) {
args.MethodExecutionTag = tag2;
aspect2.OnExit(args);
args.MethodExecutionTag = tag1;
aspect1.OnExit(args);
return (string)args.ReturnValue;
}
args.MethodExecutionTag = tag2;
aspect2.OnException(args);
if (args.FlowBehavior == FlowBehavior.Return || args.FlowBehavior == FlowBehavior.Continue) {
args.MethodExecutionTag = tag1;
aspect1.OnExit(args);
return (string)args.ReturnValue;
}
args.MethodExecutionTag = tag1;
aspect1.OnException(args);
if (args.FlowBehavior == FlowBehavior.Return || args.FlowBehavior == FlowBehavior.Continue) {
return (string)args.ReturnValue;
}
}
args.ReturnValue = returnValue;
args.MethodExecutionTag = tag3;
aspect3.OnExit();
args.MethodExecutionTag = tag2;
aspect2.OnExit();
args.MethodExecutionTag = tag1;
aspect1.OnExit();
return (string)args.ReturnValue;
}
Or, at least, that's how I understand it, according to the docs on return. But you're saying that in MBA it should just be the following?
public static string GetMessage() {
var aspect1 = new Aspect1();
var aspect2 = new Aspect2();
var aspect3 = new Aspect3();
var args = new MethodExecutionArgs();
// various setup
aspect1.OnEntry(args);
var tag1 = args.MethodExecutionTag;
aspect2.OnEntry(args);
var tag2 = args.MethodExecutionTag;
aspect3.OnEntry(args);
var tag3 = args.MethodExecutionTag;
if (args.FlowBehavior == FlowBehavior.Return) {
return (string)args.ReturnValue;
}
string returnValue = null;
try {
returnValue = "Hello, World!";
}
catch (Exception ex) {
args.Exception = ex;
args.MethodExecutionTag = tag3;
aspect3.OnException(args);
args.MethodExecutionTag = tag2;
aspect2.OnException(args);
args.MethodExecutionTag = tag1;
aspect1.OnException(args);
if (args.FlowBehavior == FlowBehavior.Return || args.FlowBehavior == FlowBehavior.Continue) {
return (string)args.ReturnValue;
}
}
args.ReturnValue = returnValue;
args.MethodExecutionTag = tag3;
aspect3.OnExit();
args.MethodExecutionTag = tag2;
aspect2.OnExit();
args.MethodExecutionTag = tag1;
aspect1.OnExit();
return (string)args.ReturnValue;
}
Is that right?
I don't particularly mind either way, and implementing the interception aspect sounds fun. I just want to make sure I'm clear about what I'm trying to build.
from methodboundaryaspect.fody.
@Ralf1108 Your example should satisfy my use case. It's worth to think about though, what would happen if we would have a caching attribute and a retry attribute together. The MethodInterceptionAspect is a good idea, so there will be two different approach to interact with a method.
from methodboundaryaspect.fody.
@Ralf1108 What do you think? Should it run every OnEntry
and OnExit
even though that's not how PostSharp does it?
from methodboundaryaspect.fody.
Hi,
maybe I wrote it too complicated :-)
Of course I want that for each OnEntry there is a OnExit or OnException call. Sorry for the irritation.
The way it works should be as intuitive as it can be and so far post sharp has the most experience in that area. We should stick to them.
Regarding last comment from @KleaTech ( #44 (comment))
The MethodInterceptionAspect is are more general type of aspect as you can say something like "Execute the original now" and even repeat this. Where should this logic be in the MethodBoundaryAspect? Should one aspect be able to call in its OnEntry method the "Execute the original now" ? In my opinion this makes no sense.
With the MethodInterceptionAspect you can always mimic the MethodBoundaryAspect, just use a try-catch around the "Execute()" method.
If we really want this this should be an on AspectBaseClass... like in PostSharp ;-)
from methodboundaryaspect.fody.
Hi,
So is there any plan to implement "FlowBehavior.Return"? I would really like this feature as well.
from methodboundaryaspect.fody.
@keith-anders
do you plan to make an PR?
from methodboundaryaspect.fody.
I did, but I got sidetracked onto other things. If someone else wants to do it, they will probably get theirs in before me.
from methodboundaryaspect.fody.
maybe you can make your current progress public via github so someone can continue from this?
from methodboundaryaspect.fody.
Any updates on this? I need to stop my method execution or return it inside OnEntry.. In PostSharp there should be FlowBehaviour.Return for this problem.
from methodboundaryaspect.fody.
@keith-anders any update on the pull-request? Maybe make it public so that someone else can continue? This is exactly the feature I need.
Thanks,
from methodboundaryaspect.fody.
Sorry about that, everyone. I'll endeavor to get something in within 48 hours from now.
from methodboundaryaspect.fody.
I just submitted #61 that should implement this. Here is what I implemented:
After each OnEntry
is run: the code checks for FlowBehavior.Return
. If found, then all previous aspects (whether or not they had an OnEntry
to run) will get their OnExit
called in reverse order. Setting the ReturnValue
somewhere either in the aborting OnEntry
or in one of the OnExit
s is important.
After each OnException is run: the code checks for FlowBehavior.Return
or FlowBehavior.Continue
which are treated identically. In a synchronous method, one of these would cause the remaining aspects to get their OnExit
s run instead of their OnException
s. In an async method, the aspect that set the flow behavior will be the last aspect to run: no others will run after. It must also set the ReturnValue
which will be set as the result of the task. The remaining aspects that might have had their OnException
s run will instead not be called.
No behavior is attached to OnExit
since at that point it's too late to change the means of exiting the function.
Once FlowBehavior.Return
is set by one aspect, from either OnEntry
or OnException
, it is never checked again--the Return
behavior persists for the duration of that method call.
I did not implement MethodInterceptionAspect
; that is a much larger project for another day.
I hope this satisfies everyone's needs, if not as conveniently as you might have hoped.
from methodboundaryaspect.fody.
@Ralf1108 Can you look over the PR?
from methodboundaryaspect.fody.
PR looks good, but we have to ensure that we mimic PostSharp behavior for FlowBehavior. Or else migrators to MBA could get confused.
@keith-anders Does this match the PostSharp behavior?
from methodboundaryaspect.fody.
Here are the behaviors I implemented in a more line-item format:
- OnEntry sets FlowBehavior.Return => use args.ReturnValue, run the previous aspects' OnExit in reverse order, do not execute the unwoven method.
- Non-state machine methods: OnException sets FlowBehavior.Return or FlowBehavior.Continue => use args.ReturnValue, run the previous aspects' OnExit in reverse order, do not rethrow the exception.
- Yield return: untested. I believe MBA.Fody does not officially support this yet.
- Async: OnException sets FlowBehavior.Return or FlowBehavior.Continue =>
a. Use args.ReturnValue to set the result of the task.
b. Do not run any of the OnExits or any remaining OnExceptions.
Item 1, 2, and 4a are exactly as in PostSharp, subject to the caveat that OnExit in MBA.Fody is treated like OnSuccess in PostSharp: it is only run when the method exits normally, not in the event of an exception. That has been a difference between the two projects for years.
Item 4b is a difference from PostSharp. The issue is that PostSharp runs its OnExit and OnSuccess handlers from the state machine when the task is done, not in the original method as MBA.Fody does. I could have added the OnExit calls to the exception handler, but to do so now would mean that OnExit would be called twice but only in the event of an exception, which I think is more confusing than what I implemented. I'm not opposed to the happy path OnExit call being moved inside the state machine, but in my opinion that is out of scope for this issue. Adding the OnExit handlers to the async OnException should wait until that happens so that the OnExits don't get run twice.
from methodboundaryaspect.fody.
Thanks for the clarification!
Regarding the different behavior of MBA.Fody and PostSharp. Should we fix this (in a new issue/PR) ?
So if the remaining behavior for the synchronous calls works as in PostSharp then i will merge the PR. For upcoming problems with async methods we wait for new issues.
@KleaTech please confirm the implemented FlowBehavior with latest version so we can close this issue.
Thx Keith for your hard work 👍
from methodboundaryaspect.fody.
I think it should probably be fixed, yes. Introduce a new OnSuccess method that takes the properties of the current OnExit, and rewrite OnExit to act more like PostSharp's.
However, as that is a breaking change to behavior, it's a major version increment. It might be worthwhile to put together a number of these changes and make them all in a single major version upgrade. Just an idea.
from methodboundaryaspect.fody.
I tested my usecase with the code below. It works as expected. I'm not sure about how this compares to the PostSharp implementation, I haven't used that.
Thank You for your work! You can close the issue.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using MethodBoundaryAspect.Fody.Attributes;
namespace Cachetest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(ComplexMethod());
Console.WriteLine(ComplexMethod());
Console.ReadKey();
}
[Cache]
static int ComplexMethod()
{
Thread.Sleep(5000);
return 42;
}
}
class CacheAttribute : OnMethodBoundaryAspect
{
public static Dictionary<string, object> Cache { get; } = new Dictionary<string, object>();
public override void OnEntry(MethodExecutionArgs args)
{
var method = args.Method;
var cacheKey = method.DeclaringType.FullName + method.Name + '_' + string.Join("_", args.Arguments);
var value = Cache.FirstOrDefault(e => e.Key == cacheKey).Value;
if (value == null) return;
args.ReturnValue = value;
args.FlowBehavior = FlowBehavior.Return;
}
public override void OnExit(MethodExecutionArgs args)
{
var method = args.Method;
var cacheKey = method.DeclaringType.FullName + method.Name + '_' + string.Join("_", args.Arguments);
Cache[cacheKey] = args.ReturnValue;
}
}
}
from methodboundaryaspect.fody.
@KleaTech thx for your feedback and the example. I will then close the issue
from methodboundaryaspect.fody.
Hi Team,
I have implemented KingAOP in our projects but we are not able to invoke our cache attribute in DataContext level. Ca you please suggest me hw can we invoked or anything is missing for this.
Thanks in advance.
from methodboundaryaspect.fody.
@Rahul9179
please create a new issue for your request and also a small demo code so we can understand what exactly is the problem or your expectations. Your two sentences are to general... something doesn't work...
If you can provide more specifics, best would be a test case in code, then investigation would be easier and also faster
from methodboundaryaspect.fody.
@Rahul9179
also you wrote that you use KingAOP which is a totally different project than our MethodBoundaryAspect.
If your problems is connected to the usage of KingAOP then you would get better answers when creating an issue on their repository.
from methodboundaryaspect.fody.
Related Issues (20)
- Unable to set debug breakpoint in methods with sequence points HOT 4
- v2.0.148 nuget publish failed HOT 4
- How to access ILogger 'injected' property to 'log' on exception? HOT 4
- Aspect doesn't work for extension methods HOT 1
- Decorating "async void" method throws weaver exception for aspect with OnException override HOT 9
- How to debug/get more info about `Sequence contains no matching element` error? HOT 2
- The package causes wrong version of 'System.Private.CoreLib' to be referenced in my app HOT 23
- Is CompileTimeValidate() supported? HOT 2
- Can MethodBoundaryAspect.Fody be used to apply aspects to third-party NuGet packages without modifying their source code? HOT 2
- Throwing "Failed to execute weaver" exception on 1 (out of 19) VB project HOT 1
- InvalidOperationException is thrown by .Single call when there's an aspect inheritance HOT 2
- Support for AsyncVoidMethodBuilder, AsyncValueTaskMethodBuilder and AsyncIteratorMethodBuilder HOT 1
- Can an asynchronous interceptor abstract class be provided for out of the box use
- Support for Double? HOT 2
- Issue when debugging intercepted methods that contain if statements HOT 6
- Not able to read attributes from Properties HOT 1
- C#11 introduce Attribute with Generics. Do you have any plan? HOT 5
- Exception logging only on highest call stack level? HOT 1
- Async & InvalidProgramException : Common Language Runtime detected an invalid program. HOT 7
- Dotnet versions are out of date
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from methodboundaryaspect.fody.