views:

220

answers:

4

I need to intercept the console output stream(s) in order to capture it for a log but still pass things through to the original stream so the application works properly. This obviously means storing the original Console.Out TextWriter before changing it with Console.SetOut(new MyTextWriterClass(originalOut)).

I assume the individual operations to get the Out property and to call the SetOut() method are implemented by Console in a thread-safe manner. But I'd like to make sure that some other thread (eg. running client application code that I don't control and can't expect to change, and so I can't rely on my own custom locking scheme) can't accidentally change it in between my get and set and end up getting overwritten by my change to it (breaking their application's behavior!). Since the other code may simply call SetOut(), my code should ideally get the same lock used internally by Console (assuming there is one).

Unfortunately, Console is a (static) class, not an instance, so you can't just lock (Console). And looking in the class documentation there does not seem to be any mention of locking. This is not the normally-expected usage of these Console methods, but there should be some safe way of doing this as an atomic operation.

Failing a standard locking scheme, is there some other way to ensure this? For such a short critical section (and done only once), even momentarily blocking all other threads might be acceptable, if that's the only way to do it. We're using C# and .NET2.0.

If not even that is possible (without disrupting the client application), then we'll just have to rely on it being very unlikely that the client application would redirect its console output and happen to do it in between our get and set operations. I'd just like to cover all the bases, just in case.

Edit: Now that we have a concrete answer with example code, I've reworded the question title to more generally reflect the use cases where the answer(s) can help, to be more clear. Also, added a tag for "atomic".

A: 

If you can do this early in Main() you'll have a much better chance of avoiding any race conditions, especially if you can do it before something creates a worker thread. Or in the static constructor of some class.

If Main() is marked with the [STAThread] attribute it will be running in a single threaded apartment so that should help too.

devstuff
Unfortunately, we're a module dynamically attached to client applications (eg. via app.config) and have no control over the application Main() nor an expectation that it can even be changed. I'll look into what [STATthread] does to see if it can help in some other way, but I'd tend to think not.
Rob Parker
+1  A: 

If you look at the implementation for SetOut it looks thread safe to me:

[HostProtection(SecurityAction.LinkDemand, UI=true)]
public static void SetOut(TextWriter newOut)
{
    if (newOut == null)
    {
        throw new ArgumentNullException("newOut");
    }
    new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
    _wasOutRedirected = true;
    newOut = TextWriter.Synchronized(newOut);
    lock (InternalSyncObject)
    {
        _out = newOut;
    }
}

Edit

The cloest thing to a solution I could come up with is using reflection to get their InternalSyncObject and then lock on it.

A word of caution, this is an extremly bad idea and should only be used when no other option exists. You could cause the framework to behave unexpectadly and crash the process.

You will also need to pay attention to any service packs and major releases making sure the internal variable is still used. Since its internal there is no promise that it will be there in the next release. Write your code defensivley and try and degrade the user experience nicely should you not the object with reflection.

Good luck:-)

JoshBerke
Yeah, but if another thread does a SetOut() right as I read Out, they could get the lock before my own SetOut() does, so my change to it would then overwrite their change without knowing about them. Then my object gets the output but doesn't pass it on where they wanted it sent, which is bad.
Rob Parker
I'd not recommend this but you could try and get a lock on their InternalSyncObject you could use reflection to do this if your running in in a security context which allows this. Hmm other then that I am out of ideas.
JoshBerke
Hmm, that's a thought, because if I could get that exact object to lock on it, that would be ideal. Do you know what the security access requirements would be for that reflection to work? I'm guessing that it might not be too likely to apply for us, but maybe it would be more likely than I think.
Rob Parker
I'm not to up on CAS but I believe there is a restriction if your running in either medium or low trust environment on using reflection.
JoshBerke
I think the idea of using reflection to find the Console class's InternalSyncObject is the closest to a real answer, but I can't pick a comment as the accepted answer.Josh, if you want to write it up as a more complete answer, I'd be able to finally pick an answer on this and give you credit.
Rob Parker
I'm not sure it'll actually be worth doing, or just how to get there through reflection, but it's apparently the only approach to try. With the framework source code available now (?), we can at least check how InternalSyncObject is being used to look for possible problems with the approach.
Rob Parker
Without the source code you could always use reflector...good luck
JoshBerke
A: 

In general, I would use the security model to keep client code from redirecting the Console while you're not looking. I'm sure that requires Full Trust, so if client code is running with Partial trust, it won't be able to call SetOut().

Sean Reilly
Well, we're being attached to *their* application, so they're the ones picking the security model I would think, and can redirect the Console, too.It just creates a timing issue and race-condition in which we could break their expected redirected output when trying to attach to log it for them.
Rob Parker
A: 

Okay, now I've had some time to actually work up an implementation of the reflection approach Josh suggested. The basic code (in my ConsoleIntercepter class, which inherits from TextWriter) is:

private static object GetConsoleLockObject()
{
    object lockObject;
    try
    {
        const BindingFlags bindingFlags = BindingFlags.GetProperty |
            BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
        // It's currently private, but we'd be happy if it were public, too.
        Type consoleType = typeof(Console);

        lockObject = consoleType.InvokeMember("InternalSyncObject", bindingFlags,
                                              null, null, null);
    }
    catch
    {
        lockObject = null; // Return null on any failure.
    }
    return lockObject;
}
public static void RegisterConsoleIntercepter()
{
    object lockObject = GetConsoleLockObject();
    if (lockObject != null)
    {
        // Great!  We can make sure any other changes happen before we read
        // or after we've written, making this an atomic replacement operation.
        lock (lockObject)
        {
            DoIntercepterRegistration();
        }
    }
    else
    {
        // Couldn't get the lock object, but we still need to work, so
        // just do it without an outer lock, and keep your fingers crossed.
        DoIntercepterRegistration();
    }
}

Then the DoIntercepterRegistration() would be something like:

private static void DoIntercepterRegistration()
{
    Console.SetOut(new ConsoleIntercepter(Console.Out));
    Console.SetError(new ConsoleIntercepter(Console.Error));
}

That's just for the lock-protected atomic replacement of Out and Error; obviously the actual interception, handling, and passing on to the previous TextWriters requires additional code, but that's not part of this question.

Note that (in the source code provided for .NET2.0) the Console class uses InternalSyncObject to protect initialization of Out and Error and to protect SetOut() and SetError(), but it does not use the lock around reads of Out and Error once they have been previously initialized. This is sufficient for my particular case, but could have atomicitiy violations if you did something more complex (and crazy); I can't think of any useful scenarios with that problem, but deliberately pathological ones can be imagined.

Rob Parker