RSS 2.0

Personal Info:

Joe Send mail to the author(s) is a lead architect on an OS incubation project at Microsoft, and was the architect for Parallel Extensions to .NET. He is an author and frequent speaker.

Blogroll:
Other
News
 C|Net
 Kuro5hin
 The Register
Technology
 <?xmlhack?>
 Daily WTF
 DevX
 Hacknot
 Java Today
 Microsoft Top 10 Downloads
 MSDN
 MSDN: "Longhorn"
 MSDN: XML Developer Center
 Slashdot
 Techdirt
 theserverside.com
 W3C
 Web Pages That Suck
 XML Cover Pages
 XML Journal
 xml.com
Technology Blogs
 Aaron Skonnard [PluralSight]
 Adam Bosworth [Google]
 Andy Rich [MS/C++]
 Arpan Desai [MS/XML]
 BCL Team [MS]
 Bill Clementson [Lisp]
 Bill de hÓra
 Bruce Eckel [J]
 Bruce Tate [J]
 Casey Chestnut
 Cedric Beust [Google]
 Chris Anderson [MS/Avalon]
 Chris Lyon [MS]
 Christian Weyer
 Clemens Vasters [newtelligence]
 Craig Andera [PluralSight]
 Dan Sugalski [Parrot]
 Daniel Cazzulino
 Dave Chappel
 Dave Roberts [Lisp]
 Dave Thomas [PragProg]
 Dave Winer
 Dion Almaer [J]
 Don Demsak
 Doug Purdy [MS/Indigo]
 Drew Marsh
 Eric Gunnerson [MS]
 Eric Rudder [MS]
 Eric Sink
 Fritz Onion [PluaralSight]
 Gavin King [J/Hibernate]
 Grady Booch [IBM]
 Hervey Wilson [MS/Indigo]
 Hillel Cooperman [MS/Shell]
 Howard Lewis Ship [J/Apache]
 Ingo Rammer [PluralSight]
 James Gosling [J/Sun]
 James Strachan [J/Groovy]
 Jason Matusow [MS/OSS]
 Jeffrey Schlimmer [MS/Indigo]
 Joe Beda [Google]
 Joel Spoelsky
 Jon Udell
 Josh Ledgard [MS/Evang]
 Joshua Allen [MS]
 Lambda
 Larry Osterman [MS]
 Maoni Stephens [MS/CLR]
 Mark Fussell [MS/XML]
 Martin Fowler
 Martin Gudgin [MS/Indigo]
 Me
 Michael Howard [MS]
 Miguel de Icaza [Mono]
 Mike Clark
 Omri Gazitt [MS/Indigo]
 Pat Helland [MS/PAG]
 Pinku Surana
 Raymond Chen [MS]
 Rich Lander [MS/CLR]
 Rob Howard
 Rob Relyea [MS/Avalon]
 Robert Cringely
 S. Somasegar [MS/DevDiv]
 Sam Gentile
 Scoble [MS/Evang]
 Scott Guthrie [MS/WebNet]
 Scott Hanselman
 Sean McGrath [J]
 Simon Fell
 Stanley Lippman [MS/C++]
 Steve Maine
 Steve Swartz [MS/Indigo]
 Steve Vinoski
 Steven Clarke [MS/Usability]
 Stuart Halloway
 Ted Leung
 Ted Neward [DM]
 Tim Bray [Sun]
 Tim Ewald [Mindreef]
 Tim O'Reilly
 Werner Vogels [Amazon]
 Wintellect
 Yasser Shohoud [MS/Indigo]
Top 20
 Brad Abrams [MS/CLR]
 Chris Brumme [MS/CLR]
 Chris Sells [MS/Ultra]
 Cyrus Najmabadi [MS/C#]
 Dominic Cooney [MS/XAF]
 Don Box [MS/Ultra]
 Don Syme [MS/R]
 Guido van Rossum [Python]
 Herb Sutter [MS/C++]
 Ian Griffiths
 Jason Zander [MS/CLR]
 Jim Hugunin [MS/CLR]
 Joel Pobar [MS/CLR]
 Krzysztof Cwalina [MS/CLR]
 Patrick Logan
 Paul Graham
 Rico Mariani [MS/CLR]
 Rory Blyth [MS/DN]
 Sam Ruby
 Wesner Moise
VC/Business Blogs
 Ed Sim
 Fred Wilson
 Jonathan Schwartz [J/Sun]
 Lawrence Lessig [Stanford]
 Mark Cuban
 Michael Hyatt
 Pierre Omidyar
 Ross Mayfield
 VentureBlog
 Weekly Read
Wine, Food & Tea
 The Silk Road of Wine
 Vinography: a wine blog
 Wine Whys

Disclaimer:
The content of this site are my own personal opinions and do not represent my employer's view in anyway.

© 2010, Joe Duffy

 
 Monday, February 23, 2009

Pop quiz: Can this code deadlock?

SpinLock slockA = new SpinLock();

SpinLock slockB = new SpinLock();

 

Thread 1             Thread 2

~~~~~~~~             ~~~~~~~~

slockA.Enter();      slockB.Enter();

slockA.Exit();       slockB.Exit();

slockB.Enter();      slockA.Enter();

slockB.Exit();       slockA.Exit();

The answer, as I'm sure you suspiciously guessed, is "it depends."

I previously posted some thoughts about whether a full fence is required when exiting the lock.  In that post, I focused primarily on timeliness.  But what might be even more frightening is that the answer to my question above is yes, provided two things:

1. Exit doesn't end with a full fence.
2. Enter doesn't start with a full fence.

Just making Exit a store release and Enter a load acquire is insufficient.  Here's why.

Imagine a super simple spin lock that satisfies our deadlock criteria:

class SpinLock {

    private volatile int m_taken;

 

    public void Enter() {

        while (true) {

            if (m_taken == 0 &&

                    Interlocked.Exchange(ref m_taken, 1) == 0)

                break;

        }

    }

 

    public void Exit() {

        m_taken = 0;

    }

}

Clearly Exit satisfies #1.  The technique of using an ordinary read of m_taken before resorting to the XCHG call is often known as a TATAS (test-and-test-and-set) lock, and this can help alleviate contention.  And it also means we will satisfy #2 above.

To see why deadlock is possible, imagine the following (fully legal) compiler transformation.  The compiler first inlines everything, so for Thread 1 we have:

Thread 1

~~~~~~~~

while (true) {

    if (slockA.m_taken == 0 &&

            Interlocked.Exchange(ref slockA.m_taken, 1) == 0)

        break;

}

slockA.m_taken = 0;

while (true) {

    if (slockB.m_taken == 0 &&

            Interlocked.Exchange(ref slockB.m_taken, 1) == 0)

        break;

}

slockB.m_taken = 0;

What has to happen next is pretty subtle.  It's even unlikely a compiler would do this intentionally (as far as I can tell).  But it's entirely legal to morph the above code into something like this:

Thread 1

~~~~~~~~

while (true) {

    if (slockA.m_taken == 0 &&

            Interlocked.Exchange(ref slockA.m_taken, 1) == 0)

        break;

}

while (slockB.m_taken == 0) ;;

slockA.m_taken = 0;

if (Interlocked.CompareExchange(ref slockB.m_taken, 1) != 0)

    while (slockB.m_taken != 0 ||

            Interlocked.Exchange(ref slockB.m_taken, 1) != 0) ;;

slockB.m_taken = 0;

The load(s) of slockB.m_taken have moved before the store to slockA.m_taken; this is legal, even if they are both marked volatile.  A load acquire can move above a store release, and the code remains functionally equivalent.  Now, the code required to fix up this code motion is pretty hokey.  We clearly can't do the XCHG before the store to slockA.m_taken, so we need to try it afterwards.  But that brings about an awkward transformation: if it fails, we must effectively do what the original code did, spinning until we acquire the slockB lock.

Do you see the deadlock yet?

Imagine the compiler did similar code motion on Thread 2:

Thread 2

~~~~~~~~

while (true) {

    if (slockB.m_taken == 0 &&

            Interlocked.Exchange(ref slockB.m_taken, 1) == 0)

        break;

}

while (slockA.m_taken == 0) ;;

slockB.m_taken = 0;

if (Interlocked.CompareExchange(ref slockA.m_taken, 1) != 0)

    while (slockA.m_taken != 0 ||

            Interlocked.Exchange(ref slockA.m_taken, 1) != 0) ;;

slockA.m_taken = 0;

Oh no!  See it now?

If Thread 1 and Thread 2 both enter the critical regions for slockA and slockB at the same times, they will end up spin-waiting for the other to leave before exiting their respective lock.

Boom: deadlock.


 

2/23/2009 8:59:14 PM (Pacific Standard Time, UTC-08:00)  #    Comments [22]

 

Recent Entries:

Search:

Browse by Date:
<February 2009>
SunMonTueWedThuFriSat
25262728293031
1234567
891011121314
15161718192021
22232425262728
1234567

Browse by Category:

Notables:

Currently Up To:

Reading...

Listening...

Watching...