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

5.2 localized Operation cancelled by user and does not have a specific error code #2424

Closed
ionmincu opened this issue Mar 21, 2024 · 15 comments
Labels
💡 Enhancement Issues that are feature requests for the drivers we maintain.

Comments

@ionmincu
Copy link

ionmincu commented Mar 21, 2024

Is your feature request related to a problem? Please describe.

In MDS 5.2 when Operation canceled by user occurs there is no way to tell from the exception that the operation was canceled.
We can't just look at the token, the application is huge, and we are using ApplicationInsights to track exceptions using ITelemetryProcessor to add custom dimentions on which exceptions to ignore.

Now because the exception message is Localized we can't reliably tell if the operation was from a canceled token.
For example when the Current culture to ja.

image

image

image

Describe the solution you'd like

  • add an option not to automatically translate the error message
  • add a specific error number for this case

Describe alternatives you've considered

A clear and concise description of any alternative solutions or features you've considered.

How to repro

See te attached zip file for repro project. Or inline here:

Expand code example

EfExample.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="EntityFramework" Version="6.4.4" />
    <PackageReference Include="ErikEJ.EntityFramework.SqlServer" Version="6.6.9" />
  <PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
    <!--<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.5" />-->
  </ItemGroup>

</Project>

Program.cs

using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Globalization;

namespace EfExample
{
    internal class Program
    {
        private const string MyConnectionString = "Server=.\\sqlexpress;Database=SchoolDb;User ID=sa;Password=mypassword;TrustServerCertificate=True";

        static async Task Main(string[] args)
        {
            var context = new SchoolContext(MyConnectionString);
            var allCourses = await context.Courses.ToListAsync();

            var simulateUseRequestLocalisation = true;

            if (simulateUseRequestLocalisation)
            {
                var culture = new CultureInfo("ja");
                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;
            }

            try
            {
                var cts = new CancellationTokenSource(200);
                await context.Database.ExecuteSqlCommandAsync("WAITFOR DELAY '00:00:10';", cts.Token);
                //var resuult = await context.Courses.Where(x => x.Students.Any()).ToListAsync(cancel);
            }
            catch(Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }

    [DbConfigurationType(typeof(System.Data.Entity.SqlServer.MicrosoftSqlDbConfiguration))]
    public class SchoolContext : DbContext
    {

        public SchoolContext(string connectionString) : base(connectionString)
        {
            Database.SetInitializer(new SchoolDBInitializer());
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        }
    }

    public class SchoolDBInitializer : DropCreateDatabaseAlways<SchoolContext>
    {
        protected override void Seed(SchoolContext context)
        {
            var students = new List<Student>
            {
                new Student { LastName = "S", FirstMidName = "Alex", },
                new Student { LastName = "M", FirstMidName = "Bob", }
            };

            var courses = new List<Course>
            {
                new Course { Title = "Math", Credits = 100 },
                new Course { Title = "Astro", Credits = 200 },
            };

            context.Students.AddRange(students);
            context.Courses.AddRange(courses);

            context.SaveChanges();
            

            base.Seed(context);
        }
    }

    public class Student
    {
        [Key]
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
    }

    public class Course
    {
        [Key]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public virtual ICollection<Student> Students { get; set; }
    }
}

Additional context

  • we are using Ef6 with ErikEJ Adapter to MDS ErikEJ.EntityFramework.SqlServer
    Stacktrace.
Microsoft.Data.SqlClient.SqlException: 'A severe error occurred on the current command.  The results, if any, should be discarded.
Operation cancelled by user.'

   at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose)
   at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at Microsoft.Data.SqlClient.SqlCommand.InternalEndExecuteNonQuery(IAsyncResult asyncResult, Boolean isInternal, String endMethod)
   at Microsoft.Data.SqlClient.SqlCommand.EndExecuteNonQueryInternal(IAsyncResult asyncResult)
   at Microsoft.Data.SqlClient.SqlCommand.EndExecuteNonQueryAsync(IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location ---
   at System.Data.Entity.Core.Objects.ObjectContext.<ExecuteStoreCommandInternalAsync>d__181.MoveNext()
   at System.Data.Entity.Core.Objects.ObjectContext.<ExecuteInTransactionAsync>d__156`1.MoveNext()
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.<ExecuteAsyncImplementation>d__6`1.MoveNext()
   at System.Data.Entity.Core.Objects.ObjectContext.<ExecuteStoreCommandInternalAsync>d__180.MoveNext()
@JRahnama JRahnama added the 🆕 Triage Needed For new issues, not triaged yet. label Mar 21, 2024
@DavoudEshtehari
Copy link
Contributor

@ionmincu, could you add more info like the underlaying operating system, SqlClient version and if it's an issue starting from a specific SqlClient version, besides providing a simple console app to repro the issue?

@DavoudEshtehari DavoudEshtehari removed the 🆕 Triage Needed For new issues, not triaged yet. label Mar 26, 2024
@ionmincu
Copy link
Author

ionmincu commented Mar 27, 2024

See te attached zip file for repro project. Or inline here:

Expand code example

EfExample.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="EntityFramework" Version="6.4.4" />
    <PackageReference Include="ErikEJ.EntityFramework.SqlServer" Version="6.6.9" />
  <PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
    <!--<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.5" />-->
  </ItemGroup>

</Project>

Program.cs

using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Globalization;

namespace EfExample
{
    internal class Program
    {
        private const string MyConnectionString = "Server=.\\sqlexpress;Database=SchoolDb;User ID=sa;Password=mypassword;TrustServerCertificate=True";

        static async Task Main(string[] args)
        {
            var context = new SchoolContext(MyConnectionString);
            var allCourses = await context.Courses.ToListAsync();

            var simulateUseRequestLocalisation = true;

            if (simulateUseRequestLocalisation)
            {
                var culture = new CultureInfo("ja");
                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;
            }

            try
            {
                var cts = new CancellationTokenSource(200);
                await context.Database.ExecuteSqlCommandAsync("WAITFOR DELAY '00:00:10';", cts.Token);
                //var resuult = await context.Courses.Where(x => x.Students.Any()).ToListAsync(cancel);
            }
            catch(Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }

    [DbConfigurationType(typeof(System.Data.Entity.SqlServer.MicrosoftSqlDbConfiguration))]
    public class SchoolContext : DbContext
    {

        public SchoolContext(string connectionString) : base(connectionString)
        {
            Database.SetInitializer(new SchoolDBInitializer());
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        }
    }

    public class SchoolDBInitializer : DropCreateDatabaseAlways<SchoolContext>
    {
        protected override void Seed(SchoolContext context)
        {
            var students = new List<Student>
            {
                new Student { LastName = "S", FirstMidName = "Alex", },
                new Student { LastName = "M", FirstMidName = "Bob", }
            };

            var courses = new List<Course>
            {
                new Course { Title = "Math", Credits = 100 },
                new Course { Title = "Astro", Credits = 200 },
            };

            context.Students.AddRange(students);
            context.Courses.AddRange(courses);

            context.SaveChanges();
            

            base.Seed(context);
        }
    }

    public class Student
    {
        [Key]
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
    }

    public class Course
    {
        [Key]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public virtual ICollection<Student> Students { get; set; }
    }
}

Basically in 5.1.5 the message was not localized, and in 5.2.0 it is localized to the request locale.
We would like it if this message is not localized, or it should have some specific error code to be able to ignore it in alerts.

OS:

  • this happens in Windows local env for the repro
  • seen many times in linux production

EfExample.zip

@roji
Copy link
Member

roji commented Mar 27, 2024

Note the relationship with #26 - at least for async operations, SqlClient should be throwing TaskCanceledException which is the standard way to indicate cancellation.

@ionmincu
Copy link
Author

ionmincu commented Mar 27, 2024

TaskCanceledException would be fine, but I understand that this might be seen as a major breaking change.

I mostly see 5.2 making a breaking change from 5.1.5 (by translating exception messages).
I would be satisfied with one of the 2 solutions (that could be implemented quickly in a minor release):

  • add environment variable to suppress localization of messages (or some other boolean switch to suppress localization)
  • add some kind of marker / error code to indicate this is really an Operation Canceled exception

@sebastienros
Copy link
Member

We are seeing the same issue while using Aspire (not Aspire specific) when running on Linux (at least WSL) and MacOS (customer reported). dotnet/aspire#1023 (comment)

@ErikEJ
Copy link
Contributor

ErikEJ commented Apr 1, 2024

@sebastienros and reverting to 5.1.5 "fixes" this?

@sebastienros
Copy link
Member

@ErikEJ I didn't realize this was specific to a new version and only focused on the "no specific error code", I will try to confirm that, if not I will file another issue.

@sebastienros
Copy link
Member

Can still repro with 5.1.5, filing a new issue

@ErikEJ
Copy link
Contributor

ErikEJ commented Apr 1, 2024

@sebastienros Looking forward to your repro details

@ionmincu
Copy link
Author

ionmincu commented Apr 2, 2024

@ErikEJ did you check my repo details it should be the same thing for 5.1.5, just replace the version.

@JRahnama JRahnama added the 💡 Enhancement Issues that are feature requests for the drivers we maintain. label Apr 2, 2024
@DavoudEshtehari
Copy link
Contributor

DavoudEshtehari commented Apr 2, 2024

The thrown exception can be improved by including a more specific inner exception, such as TaskCanceledException as @roji mentioned, and/or avoiding the translation of the message "Operation canceled by user".
The first part is a duplicate of issue #26, and the second part can be included in it.

cc @David-Engel

@DavoudEshtehari DavoudEshtehari added the 🙌 Up-for-Grabs Issues that are ready to be picked up for anyone interested. Please self-assign and remove the label label Apr 2, 2024
@roji
Copy link
Member

roji commented Apr 2, 2024

@DavoudEshtehari note that the standard behavior wouldn't be to include TaskCanceledException as an inner exception, but rather to throw a TaskCanceledException (as the top-level exception). This is important as various code reacts differently when a TaskCanceledException is thrown (as opposed to other types). For example, if a Task is terminated because of a TaskCanceledException, its state is Canceled rather than Faulted.

So just throwing TaskCanceledException would be the standard behavior here.

@DavoudEshtehari
Copy link
Contributor

@roji Thanks for the clarification.
@ionmincu I'll close this issue, but feel free to continue the discussion here or on the related issue #26.

@DavoudEshtehari DavoudEshtehari removed the 🙌 Up-for-Grabs Issues that are ready to be picked up for anyone interested. Please self-assign and remove the label label Apr 2, 2024
@ionmincu
Copy link
Author

ionmincu commented Apr 8, 2024

@DavoudEshtehari that issue seems to be very old. I'm talking about an issue that was introduce recently in 5.2. Can we get some kind of disable translations feature for 5.3 ?

@DavoudEshtehari
Copy link
Contributor

@ionmincu This behavior is identical to .NET Framework, and you've encountered it because of adding support for localization in .NET as well. Unfortunately, there is no easy way to switch it off.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💡 Enhancement Issues that are feature requests for the drivers we maintain.
Projects
Development

No branches or pull requests

6 participants