| |
 Thursday, September 08, 2005
Nearly 20 people from the CLR Team will be at PDC next week. This includes our Product Unit Manager, some senior Architects, Program Managers, Devs, and Testers. Of course, we'd love to meet one-on-one with folks from your company, or even you individually. We have a room available, and are flexible on the times.
If you're interested, just send an email to: PDC0511s@microsoft.com. Let us know what you're interested in. Note: Being the CLR Team, we're admittedly focused on the low level goop...But almost anything's fair game: AppCompat, Security, Reliability, Performance, Concurrency, Base Class Libraries, Garbage Collection, the Future of the CLR, etc.
If you want to meet with me in particular, email me at: joedu@microsoft.com.
 Sunday, August 28, 2005
This is a fun example that illustrates a few topics I'm discussing at PDC in a couple weeks.
What, if anything, can cause Thread 2's assert below to fire?
class Foo { static Foo lastFoo;
string state; bool initialized;
public Foo() { state = "Developers, Developers, Developers!"; initialized = true; } }
// Thread #1: // Thread #2: lastFoo = new Foo(); Foo f = lastFoo; Debug.Assert(f.initialized == true && f.state != null);
For purposes of illustration, imagine that lastFoo has already been initialized to some Foo prior to threads #1 and #2 executing.
 Thursday, August 25, 2005
Following on the tail of Mr. Abrams's Channel9 video (is there one too many s's in there?), check out our video where we discuss the CLR Team's presence at the PDC (Part 1 and Part 2).
We talk with:
There were a few folks we didn't get to chat with:
In watching them, I think I frontloaded the talk in a selfish way. My two talks are first...It wasn't intentional, I swear! But oh well... Check it out!
I even made up a new word in the process: expressitivity. Doh! ;)
 Tuesday, August 23, 2005
You know you're a geek when:
- You read processor manuals for fun.
- ...
I've been deeply internalizing the memory models implemented on various flavors of x86, IA-32, AMD-64, and IA-64 lately. And then rationalizing how our various JITs manage to implement the new strengthened Whidbey memory model on each architecture. Believe it or not, I love this stuff. One of the perks of being a Microsoft employee is that you can gain access to dual-proc/dual-core/HT machines, AMD-64 and IA-64 boxes, and basically anything else you could imagine. Now if there were just more hours in the day.
Here are a few good resources if you're interested in doing some research yourself:
My PDC talk touches on some of the details of memory models briefly. I wish I could do an entire talk on cache coherency, branch prediction, pipelining, instruction reordering, and the like...But I think that would put most attendees to sleep. There needs to be more me's in the world.
 Sunday, August 14, 2005
I encountered a great quote recently while listening to Business at the Speed of Thought by Bill Gates. It really hit home with me:
"We always overestimate the change that will occur in the next two years and underestimate the change that will occur in the next ten. Don't let yourself be lulled into inaction."
Of course, the definition of "inaction" is up for grabs. Perhaps complacency would have been a more appropriate word. Action is great, but sufficient action is another thing altogether.
 Friday, August 12, 2005
Check out Soma's post about the Nullable<T> DCR we recently implemented...we referred to the project as nullbox internally. This one kept me up at night on a few occassions, but was a lot of fun. :) Huge risk, but based on lots of feedback it was the right thing to do. And the team executed perfectly, nailing our target dates at each step along the way.
I alluded to this work here and here. I was vague and avoided answering probing comments intentionally. Now I can answer them...so ask away!
The core of this change is that the IL box instruction has been modified to recognize Nullable<T>s. For non-Nullables, behavior remains the same; but upon seeing one, it inspects its HasValue property. If HasValue is true, box peeks inside the structure, extracts the T value, and boxes that instead; otherwise, box simply leaves behind a null reference. Obviously, unbox has also been changed to allow nulls to be unboxed back into Nullable<T> structures. This had a rippling effect in the CLR codebase and also required changes to late-bound semantics to mimic the static case.
The result is that given
int? x = null; object y = x;
both expressions
x == null y == null
evaluate to true. And furthermore, given
bool F<T>(T t) { return t == null; }
the following expressions
F(x) F(y)
also evaluate to true.
I intend to post a more detailed summary of the DCR over the coming week[s].
 Tuesday, August 09, 2005
I get nearly zero time to read lately. Too much stuff going on, including a toasted BIOS on my primary work PC. (Yeah, it had my PDC presentations on it...You'd think working at a storage company for many years would have taught me to back my work up. *sigh*)
But I have a rule that I must read for at least 4 hours per week. Anything less than that, and I fear brain rot. Here are some current and recent reads:
| The Design and Evolution of C++ -- Bjarne Stroustrup |
 |
9 of 10. I am thouroughly enjoying this one. It takes a historical tour of the design and evolution of the C++ language, from it's inception based on Bjarne's work with Simula in the late 70's, to his C with Classes, it's codification as C++, and its evolution from 1.0 and beyond. Great insight into why certain decisions were made, and a great way to get some context around where we are today and how we got here. |
| Fundamentals of Parallel Processing -- Harry F. Jordan, et al |
 |
9 of 10. A fairly detailed tour of the parallel processing space, with some practical advice as it pertains to modern shared memory architectures and systems. I mentioned it a few weeks back, but I'm just now reading straight through the book. Material is introduction-to-medium-level, but offers some meaty chunks (whatever that means...it just came out, sorry) in many areas. Especially good coverage on data dependence analysis. |
| Customizing the CLR -- Steven Pratschner |
 |
8 of 10. I bought this book primarily for its coverage of Constrained Execution Regions, Critical Finalization, and reliability as it pertains to our hosting APIs. But then I started reading from the beginning and couldn't stop. This book will certainly serve as a good reference for anybody doing hosting coding, and could serve as a source of endless hours of fun...just toying around with some of the cool extensibility hooks the CLR provides. Happy hacking! |
| Dude, Did I Steal Your Job? Debugging Indian Computer Programmers -- N. Sivakumar |
 |
7 of 10. Despite it's potentially offensive title, this book offers interesting insight from an Indian programmer working in the United States. Funny, entertaining, and very raw (i.e. little to no editing). I am slightly annoyed by the triple spaced typesetting, but so be it. Definitely a lighter read than my norm, but it's a welcome change. |
 Saturday, August 06, 2005
I got to work on a fun DCR with Chris Brumme back around the time we were shipping Whidbey Beta2. (DCR means Design Change Request, essentially an unplanned change to the design of a component.) We went back and forth as to whether or not to release it with Beta2, but given that the implementation would have been right up against our lock down period, the risk was too high. Thus, it'll first appear in our next CTP, RC, or whatever release comes out before Whidbey RTMs.
The Problem
The crux of the problem is this. Lots of code gets written in C# assuming that catch (Exception) is sufficient to backstop any exception a piece of CLR software can generate. It turns out that, while doing so is not CLS compliant, IL can throw just about anything. The throw instruction will happily take a reference on the stack to any managed object--not just those whose type falls into the Exception type hierarchy--and unwind the stack with it in hand.
A typical user (and even some Framework developers) write exception handling code that looks like this (without all the Console.WriteLines of course :P):
try { Console.WriteLine("Inside try..."); F(); Console.WriteLine("Exiting try"); } catch (Exception e) { Console.WriteLine("In catch ({0})", e); }
Console.WriteLine("Outside try...exiting gracefully");
Now, this will work perfectly fine if F did as follows:
static void F() { // foo... throw new InvalidOperationException(); // bar... }
InvalidOperationException derives from Exception, so the catch block picks it up. But what if F did this?
static void F() { // foo... throw 0; // bar... }
Well, thankfully you can't write that in C#. But you can in verifiable IL:
.method private hidebysig static void F() cil managed { .maxstack 1 .locals init (int32 V_0) ldc.i4.0 box [mscorlib]System.Int32 throw }
The specific type, int in this case, really doesn't matter. It could be any other reference type that doesn't somehow derive from Exception, a value type, or even a null reference!
You might turn your nose up at the idea of catching all exceptions. I did. But consider if you need to roll back sensitive state that was introduced inside the try block. I've already covered why doing this in the finally block only might not be sufficient. If F() were a virtual method that a user could override and somehow supply an object of their choosing, a malicious user could use this (along with an exception filter) to mount a nasty security attack. Coming from a Java background, I was initially very surprised how real this problem is...The world becomes much more complex when you interop so tightly with the OS. For example, the CLR has to work well with SEH primarily for situations where mixed call stacks make unmanaged-to-managed (and vice versa) transitions. Suffice it to say that the two pass model introduces lots of complexities.
The Solution
Many people think that this is inherently a C# problem. Isn't it C#, not the runtime, that forces people to think in terms of Exception-derived exception hierarchies? Certainly there is precedent that indicates throwing arbitrary objects is a fine thing for a language to do. Just take a look at C++ and Python. And furthermore, C# actually enables you to fix this problem:
try { F(); } catch { // ... }
This approach has two problems. First, the catch-all handler doesn't expose to the programmer the exception that was thrown. C# could have changed this (e.g. with TLS data exposed through a static member, e.g. Exception.GetLastThrown, or something like that). That still wouldn't solve the problem that things that aren't exceptions don't accumulate a stack trace as they pass through the stack, making them nearly impossible to debug. But probably worse, the average programmer doesn't even know this is a problem! Including those who are writing code for the Frameworks that Microsoft ships. But they really shouldn't have to know. This problem spans many languages, and it really made sense for the runtime to help them out.
We solved the problem by introducing some new behavior inside the exception subsystem of the CLR. It's mostly transparent to the user. When something gets thrown that is not derived from Exception, we instantiate a new System.Runtime.CompilerServices.RuntimeWrappedException, supply the originally thrown object as an instance field of that puppy, and propagate that instead. It's public; most people will never catch such things directly, but you can if you need to access the thing that got thrown in the first place.
This has some nice benefits. The C# user can continue writing catch (Exception), and--since RuntimeWrappedException derives from Exception--will receive any non-CLS exceptions. The try/catch block we had originally written will just work for free now. And furthermore, we now capture stack trace for everything, meaning that debugging and crash dumps are immediately much more useful. Lastly, there's still a playground for languages that wish to continue participating in throwing exceptions not derived from Exception.
Supporting Naughty Languages
This last point actually complicates the design quite a bit. We queried our language community, and perhaps not-so-surprisingly, there are a lot of compilers that can throw anything. C++/CLI is one of them. So we had to preserve the existing semantics for those languages, while still enabling C# users to get the benefits of this change. Thus was born System.Runtime.CompilerServices.RuntimeCompatibilityAttribute. The C# and VB compilers will auto-decorate any compiled assemblies with this attribute, setting its property WrapNonClsExceptions to true. The runtime keys off of that to determine whether the old or new behavior is desired. The default is that we don't surface the aforementioned wrapping behavior (although as an implementation detail, we still do it). We expect more of these compatibility-preserving changes in the future, which resulted in the somewhat generic attribute naming.
If the attribute is absent, or present and WrapNonClsExceptions is set to false, we still actually wrap the exception internally so we can (1) maintain good stack traces for debugging and (2) to cleanup and optimize some of the exception code paths that had to branch based on the type of the exception. But we unwrap it as we match it against catch handlers. And we unwrap it when we deliver it to catch filters. So these languages don't know anything ever changed.
It's actually gets a bit more complicated than this, however. For cross-language call stacks, we actually do the unwrapping based on whatever the assembly in which the catch clause's assembly wants to do. Say method M in C++/CLI assembly A throws an int; this is called by method N in C# assembly B. At throw time, we construct a new RuntimeWrappedException and use that for propagation. If assembly A catches it, all it sees is the int...It never knows we wrapped it. But if it leaks, and assembly B had wrapped the call in M with a catch (Exception), that handler will actually see a RuntimeWrappedException. Furthermore, consider if there were another C++/CLI assembly C; if N didn't catch the leaked int, it would surface in C as if it never got wrapped. This is what users expect to happen, and it composes very nicely.
Most users won't even know about this change. But hopefully their code gets more secure and robust for free.
 Friday, July 22, 2005
The CLR was designed to work very well in a COM world. This design choice is not at all surprising given the history of programming on Windows, and that the CLR began life as the COM+ 2.0 Runtime (among other temporary names). When it comes to concurrency in this world, however, there's a whole host of crap that can go wrong. Thankfully most of the time it doesn't.
Before moving on, if you have a finite amount of time, I'd recommend reading Chris Brumme's weblog on Apartments and Pumping. It's exponentially more worth your time than this post. I'm going to assume you have been introduced to at least a few of the concepts there. Most mortals on this planet haven't. You'll also want to come to my Programming w/ Concurrency talk at PDC, where I'll discuss such "to the metal" details.
OK. I've hyped it up. But I don't really have that much to say.
Using monitors for critical sections
When somebody accesses a shared piece of memory from multiple units of parallel execution, some form of locking is usually necessary. For a very small class of programmers, avoiding locks and retaining correctness is possible, but it's rocket science. Most people give up quickly if they even think to try in the first place (except for double checked locking, which is often copied from some book or website on the topic). If it's a simple primitive operation, interlocked operations might work. But in other cases, you need a coarser grained critical section-ish lock. For manager programmers, this is Monitor (i.e. 'lock' keyword in C#).
A class that has a private shared static variable, for example, would lock on it before mutating its contents. Imagine we have a class Coords:
class Coords { public int x; public int y; }
Our program decides it needs to maintain the invariant that x == y (don't ask why), and here's the code a developer might write:
class MyComponent : ServicedComponent { private static Coords c = new Coords(); void DoWork() { lock (myCoords) { myCoords.x++; DoMoreWork(); myCoords.y++; } } void DoMoreWork() { /* code that tolerates broken invariants */ } }
So long as we never leak the myCoords instance (raising the risk somebody accesses it w/out locking), we're safe. Right?
Not quite.
Enter STA
You might not have noticed that MyComponent derives from ServicedComponent. This is a ContextBoundObject that lives by all of the standard COM component rules. If it's instantiated inside an STA (Single Threaded Apartment), all access is serialized, as is the case with ordinary COM components. Now, this might seem a tad esoteric, but consider if you have a class that's called by a user who wrote their own ServicedComponent. It might seem more real, and is equally as problematic.
Chris's article above talks at great length about message pumping. STAs have to pump messages, otherwise queued messages could get starved. For UI applications, this pisses users off. For other applications, it can lead to fairness issues at best and incorrect code at worst. We pump for you so you don't need to worry about it, but we might do it in places you might not expect. This ends up being nearly anywhere you can block.
Let's pretend DoMoreWork above did this:
void DoMoreWork() { Thread.CurrentThread.Join(0); }
Join waits for the target thread to complete execution or the timeout to expire, whichever comes first. Since we call it on our own thread, it should be clear which occurs first. (You are still awake, right?)
When you pump, code can reenter on top of your existing stack. Let's look at the entire snippet of code:
[ComVisible(true)] public class MyComponent : ServicedComponent { private static Coords c = new Coords();
public void DoWork(int n) { Console.WriteLine("{0}->", n);
lock (c) { // Check invariant x==y upon entry int x = c.x, y = c.y; Console.WriteLine("{0}:{1},{2}", n, x, y); Debug.Assert(x == y, string.Format("Broken invariant on entry (#{0}, {1}!={2})", n, x, y));
c.x++; DoMoreWork(); c.y++;
// Ensure invariant x==y upon exit x = c.x; y = c.y; Debug.Assert(x == y, string.Format("Broken invariant on exit (#{0}, {1}!={2})", n, x, y)); }
Console.WriteLine("{0}<-", n); }
private void DoMoreWork() { Thread.CurrentThread.Join(0); } }
Recap: The call to DoMoreWork from the DoWork function occurs while invariants are broken. And DoMoreWork (or a function that DoMoreWork calls, e.g. some opaque inside the Framework) pumps. This is a recipe for bad things.
I also added some Console.WriteLines and Debug.Asserts in there so you can watch the world fall down.
Breaking monitors with reentrancy
The situation we need to get into in order to show off this neat parlor trick is as follows:
- A bunch of MyComponents are created inside an STA server;
- We try to make a load of calls to DoWork on those components from an MTA client;
- This requires that the MTA code reenter the STA to execute;
- Our STA thread pumps while invariants are broken, thus reentering another set of work (and enabling it to see us in an inconsistent state).
It's not quite as difficult as it sounds, thanks to the CLR's accomodating interaction with the world of COM.
class Program { const int threadCount = 5;
[STAThread] static void Main() { // Create our components in our STA server (note the STAThread on Main) MyComponent[] components = new MyComponent[threadCount]; for (int i = 0; i < threadCount; i++) components[i] = new MyComponent();
// Instantiate a bunch of MTA threads to work on the STA component List<Thread> threads = new List<Thread>(threadCount); for (int i = 0; i < threadCount; i++) { int v = i; Thread t = new Thread(delegate () { components[v].DoWork(v); }); t.SetApartmentState(ApartmentState.MTA); // default--here for illustration threads.Add(t); }
// Let 'em loose threads.ForEach(delegate (Thread t) { t.Start(); });
// If you haven't Aborted by now, wait for completion threads.ForEach(delegate (Thread t) { t.Join(); }); } }
This glob of code does exactly what my bullets indicate. The whole thing can be downloaded here. Note: ensure you compile this with the DEBUG symbol defined, otherwise your calls to Debug.Assert won't be present and you won't get the desired effect of being bombarded with assert dialogs.
It's quite nice that the CLR goes out of its way to marshal across contexts, moving our code over from the MTA to the thread in the STA, executing it, and marshaling back. And furthermore, the pumping it is doing is in good faith. It's trying to make our application responsive and fair.
Unfortunately, I see the following output when I run the code:
Constructing components in a STA server... Instantiating 5 MTA threads to operate on our components... Starting up MTA threads... Waiting for MTA completion... 3-> 3:0,0 3<- 2-> 2:1,1 2<- 1-> 1:2,2 0-> 0:3,2 4-> 4:4,2 4<- 0<- 1<-
Notice the "3,2" line. That prints out "x,y"... and does so at a point in the program where they should always be equal. Unfortunately, we've got reentrant code inside our lock, and it now has access to broken invariants! Your mileage may vary based on the inherent race condition. To be fair, this is also a byproduct of our decision to make monitors reentrant. But this decision was made for recursion, not reentrancy. It turns out we don't recognize the difference.
Of course, the above example doesn't demonstrate anything too terrible. But if you happened to apply some sensitive thread wide state that you intended to roll back before enabling other code to run, for example, it means you absolutely want to avoid pumping inside a critical section. That means mostly avoiding opaque method calls, even if you suspect they don't pump. In the future, they could. In practice, this is tough to acheive. And in practice, it usually doesn't matter.
I made a pretty simple mistake just now. I knew the source of the problem immediately when I saw the exception, but it's fairly interesting.
What's wrong with this code?
MyComponent[] myComponents = Create25Components(); for (int i = 0; i < 25; i++) { Thread t = new Thread(delegate () { // do some stuff myComponent[i].DoWork(); // do some more stuff }); t.Start(); }
Oops! you say? Oops! for sure.
The reference to the induction variable i gets treated like an ordinary variable C# captures inside an anonymous delegate closure. Namely that it just gets captured into a closure class which each thread shares access to. Access to i inside the thread simply dereferences the shared memory location to obtain the value during execution... not when you capture it. So assuming the parent thread is able to spin through the loop quickly, all of your threads will probably see the final result of i, which is 25. It turns out 25 is an invalid index for the array, resulting in an IndexOutOfRangeException or two. If they get a chance to run quickly, they will see some number in between, but probably not the correct one!
One (of many) solutions is to write this instead:
MyComponent[] myComponents = Create25Components(); for (int i = 0; i < 25; i++) { MyComponent mc = myComponents[i]; Thread t = new Thread(delegate () { // do some stuff mc.DoWork(); // do some more stuff }); t.Start(); }
Easy mistake.
|
|
Me
Joe  is an architect and developer on a systems incubation project at Microsoft.
Recent
Search
Browse
Disclaimer:
The content of this site are my own personal opinions and do
not represent my employer's view in anyway.
© 2013, Joe Duffy
|
|