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

Unhandled exception on Linux ignores Dispose #79155

Open
macias opened this issue Dec 2, 2022 · 12 comments
Open

Unhandled exception on Linux ignores Dispose #79155

macias opened this issue Dec 2, 2022 · 12 comments

Comments

@macias
Copy link

macias commented Dec 2, 2022

Description

When you have unhandled exception which goes through using, Dispose is ignored on Linux.

Reproduction Steps

Run code with following content on Linux machine:

 public class Disposal : IDisposable
    {
        public void Dispose()
        {
            Console.WriteLine("disposed");
        }
    }

   class Program
    {
        static void Main(string[] args)
        {
                using (new Disposal())
                {
                    throw new Exception();
                }
         }
    }

Expected behavior

Message "disposed" shown in terminal.

Actual behavior

No message.

Regression?

I don't know.

Known Workarounds

At top level wrap around everything in:

try
{
   .... your code goes here
}
catch 
{
  throw;
}

Configuration

openSUSE 15.3, x64. I see this bug when using dotnet 6.0 and 7.0.

I also tested it on Windows 10, x64 with dotnet 5.0, the program runs correctly (i.e. the message is displayed).

Other information

No response

@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Dec 2, 2022
@AaronRobinsonMSFT
Copy link
Member

/cc @janvorli

@janvorli
Copy link
Member

janvorli commented Dec 9, 2022

I have investigated it and the reason is that Unix exception handling in case of unhandled exceptions doesn't run second pass and just fails fast with unhandled exception message. That is the reason why the finally generated by using is not called. It has been that way ever since .NET 1.0. While I don't remember why I have made it that way, it seems that one of the reason was diagnosability of such exceptions. The OS generated core dump contains the whole call stack and objects only in the first pass. In the second pass, the stack is unwound, so if the core dump was generated after the 2nd pass, it would not be possible to look at the whole program state at the time of the exception.
@macias is that causing a problem in a real world application that you have or have you just noticed this behavior?

@macias
Copy link
Author

macias commented Dec 9, 2022

This is real problem and I think serious one. Of course I cannot tell about all problems, but logging and caching is affected in my case. To make sure cache is consistent state, and logging wrote/sent all the data in Dispose before disposing internals there is (in my case) some dump/flush method.

And this is how I discovered this problem, I was struggling for some time why on earth my logger does not log anything while I have "flush" in Dispose so even in case of an exception it would be called. In theory.

So this is the part of real problem. As for comment/remark I think it is serious because you cannot tell in advance what people keep in their Dispose, but they probably stick to what they learned:

Both using forms ensure that Dispose (or DisposeAsync) is called even if an exception occurs within the using block.

From: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement

The fundamentals of such importance should be kept, Linux, not Linux. IMHO.

@janvorli
Copy link
Member

janvorli commented Dec 9, 2022

Btw, there are cases when the finally is not guaranteed to be executed even on Windows:

However, if the exception is unhandled, execution of the finally block is dependent on how the exception unwind operation is triggered. That, in turn, is dependent on how your computer is set up. The only cases where finally clauses don't run involve a program being immediately stopped. An example of this would be when InvalidProgramException gets thrown because of the IL statements being corrupt. On most operating systems, reasonable resource cleanup will take place as part of stopping and unloading the process.

See https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/try-finally

@janvorli
Copy link
Member

janvorli commented Dec 9, 2022

A workaround to your problem could be to never let exceptions become unhandled by having some top level catch. In fact, if you re-throw from such catch, you can even leave the exception being unhandled. It would just make sure the finallys are invoked.

@MichalPetryka
Copy link
Contributor

A workaround to your problem could be to never let exceptions become unhandled by having some top level catch. In fact, if you re-throw from such catch, you can even leave the exception being unhandled. It would just make sure the finallys are invoked.

Couldn't the runtime automatically do that for the user?

@janvorli
Copy link
Member

janvorli commented Dec 9, 2022

We cannot do that, because then you would lose the ability to diagnose the reasons for the crash from a crash dump / core. The stack would be unwound to the point of the rethrow, so all evidence of why the unhandled exception happened would be lost.

I am trying to figure out how to keep the OS generated dump useful and still allow running the finally stuff, but so far I wasn't able to find a way to do that. If there was a way to trigger Unix core generation without exiting the process, it would solve the problem. But I don't think it is possible.

@am11
Copy link
Member

am11 commented Dec 9, 2022

The stack would be unwound to the point of the rethrow, so all evidence of why the unhandled exception happened would be lost.

throw ex; will reset the stack, but throw; will not, right? I think the question was about the latter (to make the runtime call finally without the top level try-catch).

@janvorli
Copy link
Member

janvorli commented Dec 9, 2022

throw will not reset the stack stored in the exception, but it will still unwind the stack of the thread.

@DaZombieKiller
Copy link
Contributor

If an implicit

try
{
    // ...
}
catch
{
    throw;
}

is a no-go, is it possible for the runtime to ensure that the finally block still runs consistently through some other means? A finally block not executing in this scenario seems like a really nasty inconsistency.

@janvorli janvorli removed the untriaged New issue has not been triaged by the area owner label Dec 15, 2022
@janvorli janvorli added this to the 8.0.0 milestone Dec 15, 2022
@raffaeler
Copy link

This is another very practical use-case which does not leave any other possibility out of rebooting:
dotnet/iot#1992

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants