Generics this generics that. Surely, generics were one of the main hits of .Net FW 2.0 and you all know the advantages they give you, but what about disadvantages? The books I've read on software development are hardly giving anything for disadvantages, and that's suspicios because nothing is ideal and if you gain something, that means losing something the very same moment you gain.
The pain in the ass generics give you can be described as follows:
- generic classes and interfaces with more than two type parameters are hard to understand and maintain;
- generic class, say, List<B> is not convertible to List<A> if B is subclass of A (as of .Net FW 3.5), moreover there is no direct relationship between List<A> and List<B> which could be described in terms of OO paradigm;
- generics cypher the code to some extent: when you see an instance of Mine.MyGenericType<Mine.MyType1, Mine.MyType2, Mine.MyType2> in code you have to scratch your head to figure out which parameter means what, because they only have names internal to MyGenericType and don't mean anything outside of that class (this is somewhat less of a problem when you are writing the code, thanks to intellisense);
- generics used solely for type safety scare me a bit: the main drivers for introducing generics were code reuse, type safety and performance (in that order). There is usually a way or two to avoid using generics without losing type safety.
That's my humble opinion and don't get me wrong, I absolutely love generics. Just make sure you don't rely on them too much.
Saturday, June 26, 2010
Wednesday, May 26, 2010
Buggy trace listener
Recently a colleague of mine ran into the troublesome TextWriterTraceListener. As the name suggests, it's a trace listener that writes trace messages to a file. The way you use it, as any other listener, is as follows: either you instantiate the listener in code and add it to the static Trace.Listeners collection, or let the runtime do the same thing for you by saying so in App.config -- then you dispatch trace messages to this and other trace listeners hooked up, thru the static Trace class.
The TextWriterTraceListener and the Trace class work together just fine, as long as you don't try to use them from a finalizer when your application is terminating. You say "Trace.Writeline("Hi John, got some bad news for you!")" in the finalizer of your class which happens to run -- among other cases -- during application shutdown, and doh! -- you get a marvelous error saying that the FILE has already been closed. Isn't it just nice?
(The listener references a FileStream which has a finalizer of its own and finalizers run in no particular order: chances are your finalizer (calling into Trace) will be out of luck, calling into a dead trace listener. There are several ways in which you can make that listener do a better job, but I ended up not using Trace and TextWriterTraceListener at all. This is definitely a design flaw in both Trace and that particular listener. It should be fixed from the inside, I don't want to work around this).
The TextWriterTraceListener and the Trace class work together just fine, as long as you don't try to use them from a finalizer when your application is terminating. You say "Trace.Writeline("Hi John, got some bad news for you!")" in the finalizer of your class which happens to run -- among other cases -- during application shutdown, and doh! -- you get a marvelous error saying that the FILE has already been closed. Isn't it just nice?
(The listener references a FileStream which has a finalizer of its own and finalizers run in no particular order: chances are your finalizer (calling into Trace) will be out of luck, calling into a dead trace listener. There are several ways in which you can make that listener do a better job, but I ended up not using Trace and TextWriterTraceListener at all. This is definitely a design flaw in both Trace and that particular listener. It should be fixed from the inside, I don't want to work around this).
Friday, April 9, 2010
AppDomains and true isolation
As you may very well know, .Net Application domains (or AppDomains for short) are the "lightweight processes", according to authors of several books on .Net Framework (FW) I've read, and -- to a great many developers I've spoken to. For most part I agree with the metaphor, but there is one thing the AppDomains do not provide you with, while processes do: fault tolerance. The operating system doesn't crash if one of its processes crashes. But as of .Net FW 2.0 an unhandled exception in any AppDomain in a process brings that process down. I tend to think in terms of corner cases, worst case scenarios (my analyst complains about that a lot), so for me this basically means that Appdomains hardly provide any isolation at all.
Let's look at the ways of fixing that. One way to go would be to create a custom CLR host which would override the standard policy for unhandled exceptions. I will not cover this option as I'm not an expert at customizing CLR hosts. Another way is to revert to .Net FW 1.0/1.1 policy for unhandled exceptions, AND (this is very important) to emulate .Net FW 2.0 policy for the main Appdomain of the application, and to unload other domains, should they encounter an unhandled exception. This is the way I'm going to show and explain.
First, let's prove that the problem exists. The following code crashes the process for good (see for yourself by compiling it in a console application). Run it without the debugger attached -- to not be stuck on the line where the exception is being thrown:
Not much to explain here -- an unhandled exception by now-standard .Net policy tears down the process. I agree that this is a great default policy due to possible state corruptions related to an error that went unhandled. But in case you're building a host, say for running plugins in their own AppDomains, or trying to execute a piece of code you don't trust in its own AppDomain, you don't want your host to die because a plugin has errored out, or because the code you did not trust, proved right your assumptions about it.
First step to work around the issue is to put the crappy .Net FW 1.0/1.1 unhandled exception policy back to business. According to this policy, the process is not torn down, when an exception goes unhandled, as you may know. To achieve this, paste the following to a newly created application configuration file for the Console Application you've probably created to run the code above:
Now everything runs without crashing and the program will block for years, if you let it. But that's not what we want: .Net FW 2 policy for exceptions happens to be cool, we want it back, but only for the main AppDomain. Here is what we do: we hook up the handlers for every unhandled exception in every AppDomain. When an exception propagates up the main AppDomain (it does propagate), we look which domain has it originated from, if it's not from the main AppDomain, we unload the offending AppDomain and carry on. Otherwise we tear the process down, as (not exactly, but close) the CLR does in these cases. Of course, we're working under the assumption that a failure in one AppDomain does not (and mustn't) affect the other domains. Keep in mind that this bold assumption is not always true. I don't want to bore you to death, here is the code. (Again: run it without the debugger attached -- to not be stuck on the line where the exception is being thrown.)
On my box the output of the program is as follows:
Got an exception in main application domain.
Retrieved marker: 2
Offending AppDomain: 2
Unloading the offending application domain...
Unloaded the offending application domain...
Now we've got ourselves a host! The only fun part here is in distinguishing between the exceptions that came from the main AppDomain and all other domains.
(ExceptionMarker adds a special marker to the Exception's property bag - in the subdomain, and then reads the marker in main domain; if there is no marker attached to exception, then it originates from the main AppDomain, and the process is doomed. Full source code is here.)
P.S. Originally I thought that attaching a configuration file (like the one shown above) to the domains where the code runs, would suffice. Plus I planned on taking care to unload the domains when an exception occurs inside each of them. That didn't work: legacyUnhandledExceptionPolicy seems to affect the main application's AppDomain only.
Let's look at the ways of fixing that. One way to go would be to create a custom CLR host which would override the standard policy for unhandled exceptions. I will not cover this option as I'm not an expert at customizing CLR hosts. Another way is to revert to .Net FW 1.0/1.1 policy for unhandled exceptions, AND (this is very important) to emulate .Net FW 2.0 policy for the main Appdomain of the application, and to unload other domains, should they encounter an unhandled exception. This is the way I'm going to show and explain.
First, let's prove that the problem exists. The following code crashes the process for good (see for yourself by compiling it in a console application). Run it without the debugger attached -- to not be stuck on the line where the exception is being thrown:
class Program
{
private static AppDomain _subDomain;
static void Main(string[] args)
{
_subDomain = AppDomain.CreateDomain("CodeRunningDomain");
try
{
_subDomain.DoCallBack(delegate()
{
new Thread(delegate()
{
throw new InvalidOperationException();
}).Start();
});
}
catch
{
// this block is for demonstration purposes only,
// generally it's not a good idea to catch System.Exception.
// this block is never hit, of course
}
Thread.Sleep(TimeSpan.FromDays(2)); // the process is torn down faster than in 2 days, you will see.
}
}
Not much to explain here -- an unhandled exception by now-standard .Net policy tears down the process. I agree that this is a great default policy due to possible state corruptions related to an error that went unhandled. But in case you're building a host, say for running plugins in their own AppDomains, or trying to execute a piece of code you don't trust in its own AppDomain, you don't want your host to die because a plugin has errored out, or because the code you did not trust, proved right your assumptions about it.
First step to work around the issue is to put the crappy .Net FW 1.0/1.1 unhandled exception policy back to business. According to this policy, the process is not torn down, when an exception goes unhandled, as you may know. To achieve this, paste the following to a newly created application configuration file for the Console Application you've probably created to run the code above:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<legacyUnhandledExceptionPolicy enabled="true"/>
</runtime>
</configuration>
Now everything runs without crashing and the program will block for years, if you let it. But that's not what we want: .Net FW 2 policy for exceptions happens to be cool, we want it back, but only for the main AppDomain. Here is what we do: we hook up the handlers for every unhandled exception in every AppDomain. When an exception propagates up the main AppDomain (it does propagate), we look which domain has it originated from, if it's not from the main AppDomain, we unload the offending AppDomain and carry on. Otherwise we tear the process down, as (not exactly, but close) the CLR does in these cases. Of course, we're working under the assumption that a failure in one AppDomain does not (and mustn't) affect the other domains. Keep in mind that this bold assumption is not always true. I don't want to bore you to death, here is the code. (Again: run it without the debugger attached -- to not be stuck on the line where the exception is being thrown.)
using System;
using System.IO;
using System.Threading;
namespace ProperAppDomainIsolation
{
class Program
{
private static AppDomain _subDomain;
static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException +=
new UnhandledExceptionEventHandler(MainDomain_UnhandledException);
_subDomain = AppDomain.CreateDomain("CodeRunningDomain");
_subDomain.UnhandledException +=
new UnhandledExceptionEventHandler(Subdomain_UnhandledException);
try
{
_subDomain.DoCallBack(delegate()
{
new Thread(delegate()
{
throw new InvalidOperationException();
}).Start();
});
}
catch (Exception)
{
// log, throw, do something.
}
Thread.Sleep(TimeSpan.FromDays(2));
}
static void MainDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
// runs in main AppDomain
Exception exception = e.ExceptionObject as Exception;
if (exception == null)
{
Environment.FailFast("Very descriptive message");
}
Console.WriteLine("Got an exception in main application domain.");
try
{
Marker exceptionMarker = ExceptionMarker.GetExceptionMarker(exception);
Console.WriteLine("Retrieved marker: " + exceptionMarker.Value);
Console.WriteLine("Offending AppDomain: " + _subDomain.Id);
if (exceptionMarker.Value.Equals(_subDomain.Id.ToString(), StringComparison.Ordinal))
{
new Thread(delegate()
{
try
{
Console.WriteLine("Unloading the offending application domain...");
AppDomain.Unload(_subDomain);
_subDomain = null;
Console.WriteLine("Unloaded the offending application domain...");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}).Start();
}
}
catch (InvalidOperationException)
{
// exception is not marked - originates from the main application domain. Demonstration code - a more specific exception type is required.
Environment.FailFast("Terminating to an unhandled exception in the main Application Domain.");
}
}
static void Subdomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
// runs in subdomain
Exception exception = e.ExceptionObject as Exception;
if (exception == null)
{
Environment.FailFast("Very descriptive message");
}
ExceptionMarker.MarkException(exception, new Marker(AppDomain.CurrentDomain.Id.ToString()));
}
}
}
On my box the output of the program is as follows:
Got an exception in main application domain.
Retrieved marker: 2
Offending AppDomain: 2
Unloading the offending application domain...
Unloaded the offending application domain...
Now we've got ourselves a host! The only fun part here is in distinguishing between the exceptions that came from the main AppDomain and all other domains.
(ExceptionMarker adds a special marker to the Exception's property bag - in the subdomain, and then reads the marker in main domain; if there is no marker attached to exception, then it originates from the main AppDomain, and the process is doomed. Full source code is here.)
P.S. Originally I thought that attaching a configuration file (like the one shown above) to the domains where the code runs, would suffice. Plus I planned on taking care to unload the domains when an exception occurs inside each of them. That didn't work: legacyUnhandledExceptionPolicy seems to affect the main application's AppDomain only.
Monday, February 8, 2010
lamron
"Develop a normal C# class that implements the interface you just defined".
(The fun word here is "normal".)
In overall, it's a great guideline.
(The fun word here is "normal".)
In overall, it's a great guideline.
Tuesday, January 5, 2010
Could not have said it better
“No one scheme of classification, more than any other, represents the real structure or order of nature. Nature indifferently submits to any and all divisions which we wish to make among existing things. Some classifications may [seem to] be more significant than others, but only by reference to our interests, not because they represent reality more accurately or adequately”.
This quote is from "Object Oriented Analysis and Design". I like the book a lot (although, it could have been shorter), and it's the best part there. If you stop to think about what it says, you will probably find that it's very true. Not only does this apply to object-oriented classification, but for any other classification or schema out there. I could talk for hours about this, but since I promised to keep everything short, I will just say: the life itself is incomprehensible.
This quote is from "Object Oriented Analysis and Design". I like the book a lot (although, it could have been shorter), and it's the best part there. If you stop to think about what it says, you will probably find that it's very true. Not only does this apply to object-oriented classification, but for any other classification or schema out there. I could talk for hours about this, but since I promised to keep everything short, I will just say: the life itself is incomprehensible.
Thursday, December 31, 2009
Short sentences as names of interfaces
I've always disliked that rule (most coding standards I saw, had such a rule) about starting names of interfaces with "I". It feels like the Hungarian notation is back. And I found myself using that leading "I" in such a way that the letter really means something. For example, instead of IInputValidator, I would create an interface named IValidateInput. This is a very reader-friendly name, in my opinion: it doesn't force you into using a word that doesn't really exist ("validator" is, obviously, a verb turned into a noun; it's not in the dictionary, according to Merriam-Webster). Also, this name explains what the implementer does, and explains in a natural way. "What is your responsibility?" - you ask the interface. "I validate input", - it replies.
Of course, all of the above applies only if the interface describes something that does one thing. In general, I prefer nouns and adjectives when naming interfaces.
Sunday, December 20, 2009
Countdown Latch, take #1
OK, enough ranting. It's time to post something useful. Here is a Countdown Latch I wrote some time ago. A countdown latch is a thread-safe object that has a counter associated with it. The latch allows threads to block until that counter -- you can only decrement the counter -- reaches zero. I always missed it in .Net FW, and one is going to be in FW 4 -- finally!
(UPDATE: of course, only after writing this I've discovered an easier solution -- one using Monitor.Wait() and Monitory.PulseAll()).
Here is the listing:
(UPDATE: of course, only after writing this I've discovered an easier solution -- one using Monitor.Wait() and Monitory.PulseAll()).
Here is the listing:
using System;
using System.Globalization;
using System.Threading;
namespace Primitives.Threading
{
///
/// A thread-safe counter (goes only down) that is capable
/// of blocking threads until the counter's value reaches zero.
///
public class CountdownLatch : IDisposable
{
private int currentCount;
private ManualResetEvent countHasReachedZero = new ManualResetEvent(false);
private object disposalCriticalSection = new object();
private ManualResetEvent allWaitingThreadsAreGone;
private const int MaximumNumberOfWaiters = int.MaxValue - 1;
///
/// Note: it's safe to access/manipulate the value of this
/// field only under a lock for disposalCriticalSection,
/// or using the Inrerlocked methods.
///
private int numberOfWaitingThreads;
private bool disposed;
public CountdownLatch(int initialCount)
{
if (initialCount <= 0)
throw new ArgumentOutOfRangeException(
"initialCount",
"Must be greater than zero.");
this.currentCount = initialCount;
}
///
/// Decreases the latch' counter value by one.
/// Does not throw, if the count has already reached zero.
///
///
public void Decrement()
{
lock (this.disposalCriticalSection)
{
this.MakeSureTheLatchHasNotBeenDisposed();
if (this.currentCount > 0)
{
this.currentCount--;
if (this.currentCount == 0)
{
this.countHasReachedZero.Set();
}
}
}
}
///
/// Current latch counter value.
/// This is for informational purposes only.
///
public int CurrentCount
{
get
{
return Thread.VolatileRead(ref this.currentCount);
}
}
///
/// Blocks the calling thread until the latch count becomes zero.
///
///
///
public void WaitUntilCountIsZero()
{
lock (this.disposalCriticalSection)
{
this.MakeSureTheLatchHasNotBeenDisposed();
this.IncrementNumberOfWaiters();
}
try
{
this.countHasReachedZero.WaitOne();
this.ThrowIfHandleHasBeenSetByDispose();
}
finally
{
this.DecrementNumberOfWaitersSignalingTheHandleIfDisposing();
}
}
///
/// This is an insane precaution: even having that many threads is simply impossible.
///
///
private void IncrementNumberOfWaiters()
{
this.numberOfWaitingThreads++;
if (this.numberOfWaitingThreads == MaximumNumberOfWaiters)
{
this.numberOfWaitingThreads--;
throw new InvalidOperationException(
String.Format(
"Cannot block - only as many as {0} waiting threads are supported.",
MaximumNumberOfWaiters));
}
}
private void DecrementNumberOfWaitersSignalingTheHandleIfDisposing()
{
lock (this.disposalCriticalSection)
{
this.numberOfWaitingThreads--;
if (this.disposed && (this.numberOfWaitingThreads == 0))
{
this.allWaitingThreadsAreGone.Set();
}
}
}
///
/// Blocks the calling thread until the latch' counter value
/// is zero or specified amount of time passes.
///
/// Amount of time to wait for latch'
/// counter value to reach zero.
/// True, if the latch has signaled (counter reached zero)
/// before specified Time Span elapsed; False, otherwise.
///
///
///
public bool WaitUntilCountIsZero(TimeSpan waitTimeSpan)
{
lock (this.disposalCriticalSection)
{
this.MakeSureTheLatchHasNotBeenDisposed();
if (waitTimeSpan < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(
"waitTimeSpan",
"Must be greater than TimeSpan.Zero.");
this.IncrementNumberOfWaiters();
}
try
{
bool gotZero = this.countHasReachedZero.WaitOne(waitTimeSpan);
if (gotZero)
{
this.ThrowIfHandleHasBeenSetByDispose();
}
return gotZero;
}
finally
{
this.DecrementNumberOfWaitersSignalingTheHandleIfDisposing();
}
}
private void ThrowIfHandleHasBeenSetByDispose()
{
lock (this.disposalCriticalSection)
{
if (this.disposed && (this.currentCount != 0))
throw new ObjectDisposedException(this.GetType().FullName);
}
}
///
/// Disposes of all unmanaged resources allocated by the latch.
/// (The kernel objects used, are released).
/// Also: releases all waiting threads.
///
public void Dispose()
{
bool needToWaitForWaitersToFallOff = false;
lock (this.disposalCriticalSection)
{
if (this.disposed)
return;
this.countHasReachedZero.Set();
this.disposed = true;
if (this.numberOfWaitingThreads > 0)
{
this.allWaitingThreadsAreGone = new ManualResetEvent(false);
needToWaitForWaitersToFallOff = true;
}
}
if (needToWaitForWaitersToFallOff)
{
this.allWaitingThreadsAreGone.WaitOne();
this.allWaitingThreadsAreGone.Close();
}
this.countHasReachedZero.Close();
}
private void MakeSureTheLatchHasNotBeenDisposed()
{
if (this.disposed)
throw new ObjectDisposedException(this.GetType().FullName);
}
}
}
Subscribe to:
Posts (Atom)