try – Random IT Utensils https://blog.adamfurmanek.pl IT, operating systems, maths, and more. Fri, 15 Jan 2021 05:52:01 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.2 .NET Inside Out Part 21 – Using is broken https://blog.adamfurmanek.pl/2020/07/25/net-inside-out-part-21/ https://blog.adamfurmanek.pl/2020/07/25/net-inside-out-part-21/#respond Sat, 25 Jul 2020 08:00:53 +0000 https://blog.adamfurmanek.pl/?p=3381 Continue reading .NET Inside Out Part 21 – Using is broken]]>

This is the twentieth first part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#

Today we are going to see using in details across various .NET implementations (.NET Core, .NET Framework and Mono). I explicitly mention platform differences later on. The following code:

using(var resource = new Resource()){
	// ...
}

is conceptually translated to:

var resource = new Resource();
try{
	// ...
} finally{
	if(resource != null) resource.Dispose();
}

Seems simple enough but it has a lot of gotchas.

Losing exceptions

Let’s start with the following code:

using System;
using System.Threading.Tasks;
					
public class Program
{
	public static void Main()
	{
		using(var resource = new Resource()){
			Console.WriteLine("Using");
			throw new Exception("Using failed");
		}
	}
}

public class Resource : IDisposable {
	public void Dispose(){
		Console.WriteLine("Disposing");
		throw new Exception("Disposing failed");
	}
}

We throw an exception in the try block and while cleaning up the resource (think of it as of an exception while closing a file, for instance). Output:

Using
Unhandled exception. System.Exception: Using failed
   at Program.Main()

So we can see that the resource was not cleaned up at all! Why? That’s because there is no exception handler so in some implementations the finally block is not executed. Let’s try adding it:

using System;
using System.Threading.Tasks;
					
public class Program
{
	public static void Main()
	{
		try{
			using(var resource = new Resource()){
				Console.WriteLine("Using");
				throw new Exception("Using failed");
			}
		}catch(Exception e){
			Console.WriteLine("Exception: " + e);
		}
	}
}

public class Resource : IDisposable {
	public void Dispose(){
		Console.WriteLine("Disposing");
		throw new Exception("Disposing failed");
	}
}

Output:

Using
Disposing
Exception: System.Exception: Disposing failed
   at Resource.Dispose()
   at Program.Main()

Okay, we see that the Dispose method was called and threw the exception but we lost the exception from the using block.

Let’s see if it works in Java:

import java.util.*;
import java.lang.*;
import java.io.*;
 
class Ideone
{
	public static void main (String[] args) throws java.lang.Exception
	{
		try(Resource resource = new Resource()){
			System.out.println("Trying");
			throw new RuntimeException("Trying failed");
		}
	}
}
 
class Resource implements Closeable {
	public void close(){
		System.out.println("Closing");
		throw new RuntimeException("Closing failed");
	}
}

Output:

Trying
Closing
Exception in thread "main" java.lang.RuntimeException: Trying failed
	at Ideone.main(Main.java:11)
	Suppressed: java.lang.RuntimeException: Closing failed
		at Resource.close(Main.java:19)
		at Ideone.main(Main.java:9)

Okay, try with resources in Java works correctly, prints both exceptions.

So we see C# loses exceptions. Well, let’s continue.

Faulty constructor

What happens when constructor throws?

using System;
using System.Threading.Tasks;
					
public class Program
{
	public static void Main()
	{
		using(var resource = new Resource()){
			Console.WriteLine("Using");
			throw new Exception("Using failed");
		}
	}
}

public class Resource : IDisposable {
	public Resource(){
		Console.WriteLine("Resource");
		throw new Exception("Resource failed");
	}
	
	public void Dispose(){
		Console.WriteLine("Disposing");
		throw new Exception("Disposing failed");
	}
}

Resource
Unhandled exception. System.Exception: Resource failed
   at Resource..ctor()
   at Program.Main()

So we don’t enter the using block at all. At least, this is consistent with Java:

import java.util.*;
import java.lang.*;
import java.io.*;
 
class Ideone
{
	public static void main (String[] args) throws java.lang.Exception
	{
		try(Resource resource = new Resource()){
			System.out.println("Trying");
			throw new RuntimeException("Trying failed");
		}
	}
}
 
class Resource implements Closeable {
	public Resource(){
		System.out.println("Resource");
		throw new RuntimeException("Resource failed");
	}
 
	public void close(){
		System.out.println("Closing");
		throw new RuntimeException("Closing failed");
	}
}

Resource
Exception in thread "main" java.lang.RuntimeException: Resource failed
	at Resource.<init>(Main.java:19)
	at Ideone.main(Main.java:9)

So we see that even though it looks like the resource is inside using, it is initialized before entering the try block. Something similar to old implementation of lock keyword.

Let’s fix it

There are two places we may get exceptions. One is in the using block, the other is in the cleanup code. We need to handle both of them, ideally without changing the types. Let’s take this code:

using System;
using System.Runtime.ExceptionServices;
					
public class Program
{
	public static void Main()
	{
		Exception exception = null;
		Exception exception2 = null;
		var resource = new Resource();
		try{
			Console.WriteLine("Using");
			throw new Exception("Using failed");
		}catch(Exception e){
			exception = e;
		}finally{
			try{
				if(resource != null) resource.Dispose();
			} catch(Exception e2){
				exception2 = e2;
			}
			
			if(exception != null && exception2 != null){
				throw new AggregateException(exception, exception2);
			}else if(exception != null){
				ExceptionDispatchInfo.Capture(exception).Throw();
			}else if(exception2 != null){
				ExceptionDispatchInfo.Capture(exception2).Throw();
			}
		}
	}
}

public class Resource : IDisposable {
	public void Dispose(){
		Console.WriteLine("Disposing");
		throw new Exception("Disposing failed");
	}
}

In liens 8 and 9 we prepare variables for storing exceptions. Next, we enter try block and do the same things we did previously.
In line 15 we capture the exception thrown, to be used later.
In lines 17-21 we try cleaning up the resource if there is a need.
Finally, in lines 23-28 we decide what to do. If we had two exceptions, we need to throw both of them. So we just use AggregateException. However, if we had only one of them (line 25 or 27), we rethrow it with preserving the context.

Output with two exceptions:

Using
Disposing
Unhandled exception. System.AggregateException: One or more errors occurred. (Using failed) (Disposing failed)
 ---> System.Exception: Using failed
   at Program.Main()
   --- End of inner exception stack trace ---
   at Program.Main()
 ---> (Inner Exception #1) System.Exception: Disposing failed
   at Resource.Dispose()
   at Program.Main()<---

Exception during cleanup only:

Using
Disposing
Unhandled exception. System.Exception: Disposing failed
   at Resource.Dispose()
   at Program.Main()
--- End of stack trace from previous location where exception was thrown ---
   at Program.Main()

Exception during using only:

Using
Disposing
Unhandled exception. System.Exception: Using failed
   at Program.Main()
--- End of stack trace from previous location where exception was thrown ---
   at Program.Main()

Looks much better now.

Let’s fix it even more (Mono and .NET Framework)

.NET Core doesn’t allow us, but what if we abort the thread?

using System;
using System.Threading;
using System.Runtime.ExceptionServices;
					
public class Program
{
	public static void Main()
	{
		var handle1 = new ManualResetEvent(false);
		var handle2 = new ManualResetEvent(false);
		var abortedThread = new Thread(() => {
			try{
				Exception exception = null;
				Exception exception2 = null;
				var resource = new Resource();
				try{
					Console.WriteLine("Using");
					handle1.Set();
					handle2.WaitOne(); // Interruption comes here
				}catch(Exception e){
					exception = e;
				}finally{
					try{
						if(resource != null) resource.Dispose();
					} catch(Exception e2){
						exception2 = e2;
					}

					if(exception != null && exception2 != null){
						throw new AggregateException(exception, exception2);
					}else if(exception != null){
						ExceptionDispatchInfo.Capture(exception).Throw();
					}else if(exception2 != null){
						ExceptionDispatchInfo.Capture(exception2).Throw();
					}
				}
			}catch(Exception e3){
				Console.WriteLine("Swallowing everything! " + e3);
			}

			Console.WriteLine("This shouldn't appear");
		});
		
		abortedThread.Start();
		handle1.WaitOne();
		abortedThread.Abort();
		handle2.Set();
		abortedThread.Join();
	}
}

public class Resource : IDisposable {
	public void Dispose(){
		Console.WriteLine("Disposing");
		throw new Exception("Disposing failed");
	}
}

We create two threads and synchronize them using events. The outer thread tries to kill the inner one, which is in the middle of using. Output:

Using
Disposing
Swallowing everything! System.AggregateException: One or more errors occurred. (Thread was being aborted.) (Disposing failed) ---> System.Threading.ThreadAbortException: Thread was being aborted.
  at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x00038] in <f57b24d7a4d84a82b73f0e7acb34858b>:0 
   --- End of inner exception stack trace ---
  at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x0007f] in <f57b24d7a4d84a82b73f0e7acb34858b>:0 
---> (Inner Exception #0) System.Threading.ThreadAbortException: Thread was being aborted.
  at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x00038] in <f57b24d7a4d84a82b73f0e7acb34858b>:0 <---

---> (Inner Exception #1) System.Exception: Disposing failed
  at Resource.Dispose () [0x0000c] in <f57b24d7a4d84a82b73f0e7acb34858b>:0 
  at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x00048] in <f57b24d7a4d84a82b73f0e7acb34858b>:0 <---

This shouldn't appear

Looks pretty good. We swallow the exception and print it out, we have (almost) all the details. However, we also have a line This shouldn't appear. Remember that we cannot stop the thread from aborting just like that, we need to call ResetAbort. However, in this example we change this behavior. BTW, in this weird tio Mono version, by throwing in finally you can stop the thread from aborting, as shown here.

Okay, how to fix that? We need to abort the thread again but in some weird version of Mono in tio we cannot do it from its body (we can in .NET Framework). We need to grab another one, as shown here:

using System;
using System.Threading;
using System.Runtime.ExceptionServices;

public class FixingUsing
{
    public static void Main()
    {
        var handle1 = new ManualResetEvent(false);
        var handle2 = new ManualResetEvent(false);
        var abortedThread = new Thread(() => {
            try
            {
                Exception exception = null;
                Exception exception2 = null;
                var resource = new Resource();
                try
                {
                    Console.WriteLine("Using");
                    handle1.Set();
                    handle2.WaitOne(); // Interruption comes here
                }
                catch (Exception e)
                {
                    exception = e;
                }
                finally
                {
                    try
                    {
                        if (resource != null) resource.Dispose();
                    }
                    catch (Exception e2)
                    {
                        exception2 = e2;
                    }

                    var isAborted = Thread.CurrentThread.ThreadState == ThreadState.AbortRequested;
                    Action<object> killMe = (object o) => {
                        Thread.ResetAbort();
                        if (Type.GetType("Mono.Runtime") != null)
                        {
                            var threadToKill = Thread.CurrentThread;
                            var killer = new Thread(() =>
                            {
                                threadToKill.Abort(o);
                            });
                            killer.Start();
                            killer.Join();
                        }
                        else
                        {
                            Thread.CurrentThread.Abort(o);
                        }
                    };
                    if (exception != null && exception2 != null)
                    {
                        var aggregate = new AggregateException(exception, exception2);
                        if (isAborted)
                        {
                            killMe(aggregate);
                        }
                        else
                        {
                            throw aggregate;
                        }
                    }
                    else if (exception != null)
                    {
                        if (isAborted)
                        {
                            killMe(exception);
                        }
                        else
                        {
                            ExceptionDispatchInfo.Capture(exception).Throw();
                        }
                    }
                    else if (exception2 != null)
                    {
                        if (isAborted)
                        {
                            killMe(exception);
                        }
                        else
                        {
                            ExceptionDispatchInfo.Capture(exception2).Throw();
                        }
                    }
                }
            }
            catch (ThreadAbortException e3)
            {
                Console.WriteLine("Swallowing everything! " + e3.ExceptionState);
            }

            Console.WriteLine("This shouldn't appear");
        });

        abortedThread.Start();
        handle1.WaitOne();
        abortedThread.Abort("With old data");
        handle2.Set();
        abortedThread.Join();
    }
}

public class Resource : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Disposing");
        throw new Exception("Disposing failed");
    }
}

Output:

Using
Disposing
Swallowing everything! System.AggregateException: One or more errors occurred. (Thread was being aborted.) (Disposing failed) ---> System.Threading.ThreadAbortException: Thread was being aborted.
  at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x00038] in <0bf376f98ae6495e93983a76ab993c03>:0 
   --- End of inner exception stack trace ---
---> (Inner Exception #0) System.Threading.ThreadAbortException: Thread was being aborted.
  at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x00038] in <0bf376f98ae6495e93983a76ab993c03>:0 <---

---> (Inner Exception #1) System.Exception: Disposing failed
  at Resource.Dispose () [0x0000c] in <0bf376f98ae6495e93983a76ab993c03>:0 
  at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x0004b] in <0bf376f98ae6495e93983a76ab993c03>:0 <---

We miss one last thing, the original value passed to Thread.Abort("With old data"). But it can be extracted when logging, we just need to cast the exceptions correctly.

This seems to be working a bit better. Just wrap it into a helper method and that’s all.

Bonus chatter: here you can read about doing that in Scala.

]]>
https://blog.adamfurmanek.pl/2020/07/25/net-inside-out-part-21/feed/ 0