Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would try/finally rather than a "using" statement help avoid a race condition?

Tags:

This question relates to a comment in another posting here: Cancelling an Entity Framework Query

I will reproduce the code example from there for clarity:

    var thread = new Thread((param) =>     {         var currentString = param as string;          if (currentString == null)         {             // TODO OMG exception             throw new Exception();         }          AdventureWorks2008R2Entities entities = null;         try // Don't use using because it can cause race condition         {             entities = new AdventureWorks2008R2Entities();              ObjectQuery<Person> query = entities.People                 .Include("Password")                 .Include("PersonPhone")                 .Include("EmailAddress")                 .Include("BusinessEntity")                 .Include("BusinessEntityContact");             // Improves performance of readonly query where             // objects do not have to be tracked by context             // Edit: But it doesn't work for this query because of includes             // query.MergeOption = MergeOption.NoTracking;              foreach (var record in query                  .Where(p => p.LastName.StartsWith(currentString)))             {                 // TODO fill some buffer and invoke UI update             }         }         finally         {             if (entities != null)             {                 entities.Dispose();             }         }     });  thread.Start("P"); // Just for test Thread.Sleep(500); thread.Abort(); 

I can't make sense of the comment that says

Don't use using because it can cause race condition

entities is a local variable and won't be shared if the code is re-entered on another thread, and within the same thread it would seem perfectly safe (and indeed equivalent to the given code) to assign it inside a "using" statement in the usual way, rather than doing things manually with the try/finally. Can anyone enlighten me?

like image 830
Mike Nunan Avatar asked Feb 12 '13 10:02

Mike Nunan


People also ask

How can race conditions be prevented?

To prevent race conditions from occurring you must make sure that the critical section is executed as an atomic instruction. That means that once a single thread is executing it, no other threads can execute it until the first thread has left the critical section.

What does it mean for a database to have a race condition how can we avoid them?

A race condition occurs when two or more threads can access shared data and they try to change it at the same time. Because the thread scheduling algorithm can swap between threads at any time, you don't know the order in which the threads will attempt to access the shared data.

Which of the following can cause a race condition?

When race conditions occur. A race condition occurs when two threads access a shared variable at the same time. The first thread reads the variable, and the second thread reads the same value from the variable.

What do you mean by race condition?

A race condition is an undesirable situation that occurs when a device or system attempts to perform two or more operations at the same time, but because of the nature of the device or system, the operations must be done in the proper sequence to be done correctly.


2 Answers

Yeah, there is a possible race in the using statement. The C# compiler transforms

using (var obj = new Foo()) {     // statements } 

to:

var obj = new Foo(); try {    // statements } finally {    if (obj != null) obj.Dispose(); } 

The race occurs when the thread is aborted right between the obj assignment statement and the try block. Extremely small odds but not zero. The object won't be disposed when that happens. Note how he rewrote that code by moving the assignment inside the try block so this race cannot occur. Nothing actually goes fundamentally wrong when the race occurs, disposing objects is not a requirement.

Having to choose between making thread aborts marginally more efficient and writing using statements by hand, you should first opt for not getting in the habit of using Thread.Abort(). I can't recommend actually doing this, the using statement has additional safety measures to ensure accidents don't happen, it makes sure that the original object gets disposed even when the object is re-assigned inside the using statement. Adding catch clauses is less prone to accidents as well. The using statement exists to reduce the likelihood of bugs, always use it.


Noodling on a bit about this problem, the answer is popular, there's another common C# statement that suffers from the exact same race. It looks like this:

lock (obj) {     // statements } 

Translated to:

Monitor.Enter(obj); // <=== Eeeek! try {     // statements } finally {     Monitor.Exit(obj); } 

Exact same scenario, the thread abort can strike after the Enter() call and before entering the try block. Which prevents the Exit() call from being made. This is way nastier than a Dispose() call that isn't made of course, this is almost certainly going to cause deadlock. The problem is specific to the x64 jitter, the sordid details are described well in this Joe Duffy blog post.

It is very hard to reliably fix this one, moving the Enter() call inside the try block can't solve the problem. You cannot be sure that the Enter call was made so you cannot reliably call the Exit() method without possibly triggering an exception. The Monitor.ReliableEnter() method that Duffy was talking about did eventually happen. The .NET 4 version of Monitor got a TryEnter() overload that takes a ref bool lockTaken argument. Now you know it is okay to call Exit().

Well, scary stuff that goes BUMP in the night when you are not looking. Writing code that's safely interruptable is hard. You'd be wise to never assume that code that you didn't write got all of this taken care of. Testing such code is extremely difficult since the race is so rare. You can never be sure.

like image 115
Hans Passant Avatar answered Sep 27 '22 18:09

Hans Passant


Very strange, cause using is only syntax sugar for try - finally block.

From MSDN:

You can achieve the same result by putting the object inside a try block and then calling Dispose in a finally block; in fact, this is how the using statement is translated by the compiler.

like image 30
Andrii Startsev Avatar answered Sep 27 '22 17:09

Andrii Startsev