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

Add CancellationToken support to QueryAsync overload #2138

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

itcotta
Copy link

@itcotta itcotta commented Dec 30, 2024

This change introduces an optional CancellationToken parameter to the QueryAsync method, enabling support for cancellation of asynchronous database queries. The modification ensures better resilience and control over long-running or potentially cancelable operations.

This fixes issues #2125 and #1938.

This change introduces a CancellationToken parameter to the QueryAsync method, enabling support for cooperative cancellation of asynchronous database queries. The modification ensures better resilience and control over long-running or potentially cancelable operations.

This fixes issues DapperLib#2125 and DapperLib#1938.
@PauloHMattos
Copy link

This would be a breaking change. I don't think this can me added until a new major release of Dapper

@itcotta
Copy link
Author

itcotta commented Dec 31, 2024

This would be a breaking change. I don't think this can me added until a new major release of Dapper

Hi, I'm a bit unsure why this would be considered a breaking change as the new CancellationToken parameter is optional when calling the QueryAsync method. That said I'm new to this code base, perhaps I'm missing something? However, if this is really a breaking change, then I agree that it would make most sense to wait with merging this until the next major release.

@worming004
Copy link

This is in detail, and most C# developers accept this change as it is not breaking in a common way. But there is edge cases adding an optionnal parameter is a breaking change. For example:

Here is startup case

public class Foo
{
    public static async Task Bar()
    {
        await Task.Delay(5);
        System.Console.WriteLine("Bar");
    }

}

public class MyProgram()
{
    public static async Task MyMain()
    {
        Func<Task> MyFunc = Foo.Bar;

        await MyFunc();
    }
}

When modifying to public static async Task Bar(Cancellation Token = default), then MyProgram cannot compile anymore. MyFunc expect Foo.Bar to have a definition matching Func<Task>. But the only definition is now Func<CancellationToken, Task>

Here is the change without breaking change:

public class Foo
{
    public static Task Bar()
    {
        return Bar(default);
    }

    public static async Task Bar(CancellationToken cts)
    {
        await Task.Delay(5, cts);
        System.Console.WriteLine("Bar");
    }
}

public class MyProgram()
{
    public static async Task MyMain()
    {
        Func<Task> MyFunc = Foo.Bar;

        await MyFunc();
    }
}

This is not breaking anymore.

@mgravell
Copy link
Member

mgravell commented Jan 1, 2025

To be clear: any change to the parameters is always a breaking change.

Fundamentally, there are two very different kinds of "break" we need to consider:

  • source breaking changes - things that prevent new compile runs from completing
  • runtime / "binary" breaking changes - things that prevent existing code compiled against an old version of the library from running against a newer version of the library

Changes can be either or both (or neither) kind of break.
We need to be mindful of both kinds of breaks, but since we're a commonly used library we need to be especially mindful of the second kind: the first kind of break causes a developer to have a bad 20 minutes while they change 3 lines of code with whatever change needs applying, but the second kind causes a department or entire company to have a bad day or days when a service randomly goes offline when they updated an innocent looking unrelated package that caused a transitive library update 3 steps removed, leading to MissingMethodException or similar.

So: we don't want that.

Adding a parameter - even an optional parameter - is a binary breaking: existing code compiled against the old method will simply stop working if the library is updated in this way, unless all code using it is recompiled. The compiled IL indicates a very specific method - one that will no longer be found (optional parameters don't make any difference for IL bind purposes). It is not always (or even often) the case that every package is rebuilt for every deployment - and often, there may be intermediate 3rd-party wrapper packages in the mix, meaning that it isn't even possible to simply rebuild it - you need to petition the 3rd party (who might be busy or otherwise unavailable) to update and redeploy their package - and there might even be multiple chained 3rd parties that need to update in the right order!

So, yeah. We cannot and will not just add optional parameters: instead, we need to add a new overload with the additional parameter. Note, however, that where appropriate it is possible to retire old overloads by simply removing the "this" modifier; this is not a binary break, and is not a source break as long as the remaining overloads over the existing and new use-cases. Most of the time, however, both the old and new overloads retain the "this".

Hi, I'm a bit unsure why this would be considered a breaking change as the new CancellationToken parameter is optional when calling the QueryAsync method.

I hope the above explains why this is a breaking change as written.

That said I'm new to this code base, perhaps I'm missing something?

Nothing in the above is specific to this codebase - it is fundamentally how library packages work. This is less of a problem if the only people who consume a library are in the same team, and everything will always get updated together: but if you're a public library, this is not the case.

However, if this is really a breaking change, then I agree that it would make most sense to wait with merging this until the next major release.

Even at majors, we don't wilfully make breaking changes without a very good reason. In this case, the change can be applied by adding overloads rather than parameters.

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.

4 participants