Skip to content

An extension for Fody to integrate error handling into async and TPL code

License

Notifications You must be signed in to change notification settings

Fody/AsyncErrorHandler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AsyncErrorHandler.Fody

Chat on Gitter NuGet Status

Fody.AsyncErrorHandler is a Fody extension for weaving exception handling code into applications which use async code.

See Milestones for release notes.

This is an add-in for Fody

It is expected that all developers using Fody become a Patron on OpenCollective. See Licensing/Patron FAQ for more information.

Usage

See also Fody usage.

NuGet package

https://nuget.org/packages/AsyncErrorHandler.Fody/

PM> Install-Package Fody
PM> Install-Package AsyncErrorHandler.Fody

The Install-Package Fody is required since NuGet always defaults to the oldest, and most buggy, version of any dependency.

Add to FodyWeavers.xml

Add <AsyncErrorHandler/> to FodyWeavers.xml

<Weavers>
  <AsyncErrorHandler/>
</Weavers>

Why?

Because writing plumbing code is dumb and repetitive.

How?

IL-weaving after the code is compiled, bro.

For example, imagine you've got this code to serialize an object to the filesystem:

public class DataStorage
{
    public async Task WriteFile(string key, object value)
    {
        var jsonValue = JsonConvert.SerializeObject(value);
        using (var file = await folder.OpenStreamForWriteAsync(key, CreationCollisionOption.ReplaceExisting))
        using (var stream = new StreamWriter(file))
            await stream.WriteAsync(jsonValue);
    }
}

After the code builds, the weaver could scan your assembly looking for code which behaves a certain way, and rewrite it to include the necessary handling code:

public class DataStorage
{
    public async Task WriteFile(string key, object value)
    {
        try 
        {
            var jsonValue = JsonConvert.SerializeObject(value);
            using (var file = await folder.OpenStreamForWriteAsync(key, CreationCollisionOption.ReplaceExisting))
            using (var stream = new StreamWriter(file))
                await stream.WriteAsync(jsonValue);
        }
        catch (Exception exception)
        {
            AsyncErrorHandler.HandleException(exception);
        } 
    }
}

And your application could provide its own implementation of the error handling module:

public static class AsyncErrorHandler
{
    public static void HandleException(Exception exception)
    {
        Debug.WriteLine(exception);
    }
}

Which allows you to intercept the exceptions at runtime.

What it really does

So the above example is actually a little misleading. It shows "in effect" what is inject. In reality the injected code is a little more complicated.

What async actually produces

So given a method like this

public async Task Method()
{
    await Task.Delay(1);
}

The compile will produce this

[AsyncStateMachine(typeof(<Method>d__0)), DebuggerStepThrough]
public Task Method()
{
    <Method>d__0 d__;
    d__.<>4__this = this;
    d__.<>t__builder = AsyncTaskMethodBuilder.Create();
    d__.<>1__state = -1;
    d__.<>t__builder.Start<<Method>d__0>(ref d__);
    return d__.<>t__builder.Task;
}

So "Method" has become a stub that calls into a state machine.

The state machine will look like this

[CompilerGenerated]
struct <Method>d__0 : IAsyncStateMachine
{
    // Fields
    public int <>1__state;
    public Target <>4__this;
    public AsyncTaskMethodBuilder <>t__builder;
    private object <>t__stack;
    private TaskAwaiter <>u__$awaiter1;

    // Methods
    private void MoveNext();
    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine param0);
}

The method we care about is MoveNext. It will look something like this

void MoveNext()
{
    try
    {
        TaskAwaiter awaiter;
        bool flag = true;
        switch (this.<>1__state)
        {
            case -3:
                goto Label_009F;

            case 0:
                break;

            default:
                awaiter = Task.Delay(1).GetAwaiter();
                if (awaiter.IsCompleted)
                {
                    goto Label_006F;
                }
                this.<>1__state = 0;
                this.<>u__$awaiter1 = awaiter;
                this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Target.<Method>d__0>(ref awaiter, ref this);
                flag = false;
                return;
        }
        awaiter = this.<>u__$awaiter1;
        this.<>u__$awaiter1 = new TaskAwaiter();
        this.<>1__state = -1;
    Label_006F:
        awaiter.GetResult();
        awaiter = new TaskAwaiter();
    }
    catch (Exception exception)
    {
        this.<>1__state = -2;
        this.<>t__builder.SetException(exception);
        return;
    }
Label_009F:
    this.<>1__state = -2;
    this.<>t__builder.SetResult();
}

Most of that can be ignored. The important thing to note is that it is swallowing exceptions in a catch. And passing that exception to a SetException method.

So when AsyncErrorHandler does its weaving it searches for SetException(exception); and then modifies the catch to look like this.

catch (Exception exception)
{
    this.<>1__state = -2;
    AsyncErrorHandler.HandleException(exception);
    this.<>t__builder.SetException(exception);
    return;
}

Icon

Icon courtesy of The Noun Project

About

An extension for Fody to integrate error handling into async and TPL code

Resources

License

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages