Out on the Paulinskill Viaduct

I’ve posted an account of a recent visit I made to the Paulinskill Viaduct, a crumbling concrete edifice that was once an integral part of the Delaware, Lackawanna, and Western Railroad’s “Lackawanna Cut-off” route across hilly northwestern New Jersey. Like the Pequest Fill, also a part of the same route, it is a monument both to American ingenuity, and American chutzpah in the face of nature’s obstacles. You can read the article here, and view the gallery of images from my hike here.

The Paulinskill Viaduct

Almost exactly a year ago I posted a piece here about hiking out onto the Pequest Fill, perhaps the greatest mound of dirt and rock ever pushed up in one place by the hand of man. The so-called “Lackawanna Cut-off” that rode the top of that long dike provided the Delaware, Lackawanna, and Western Railroad with a straight, 70 MPH shot across the heavily wooded, steeply ridged terrain of northwestern New Jersey. That meant steam locomotives pulling long trains filled with the rich resources of the west would no longer have to follow the circuitous “old route” with its crumbling, single-tracked tunnel at Oxford. If that goal required burying an entire river valley, well this was the age of progress. On with it!

Continue reading

Windows Explorer and Directory.Delete()

It’s probably a common scenario: you’ve got some process that, in the course of doing whatever it does, creates a directory and later deletes that same directory. If you’re working in .NET using C# you might be making a call like this:

Directory.Delete("c:\\Temp\\Test1", true);

While debugging the program you naturally open up Windows explorer and look at the file system to confirm that the program is doing what you expect. But then, for some reason it doesn’t. Instead of removing the specified folder and all its children through recursive deletes the line of code above throws an exception:

The directory is not empty.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.Directory.DeleteHelper(String fullPath, String userPath, Boolean
recursive)
at System.IO.Directory.Delete(String fullPath, String userPath, Boolean recur
sive)
at System.IO.Directory.Delete(String path, Boolean recursive)
at DirectoryTest.Program.Main(String[] args) in C:\VSProjects\DirectoryTest\Program.cs:line 31

This happened to me recently while debugging a back-end job, and it took me a few minutes of head scratching before I realized that Explorer was the culprit. Searching around on Google revealed that this little gotcha has nabbed others before me, and in some cases programmers were led to extreme solutions such as creating their own recursive delete method, and for all I know may have done so without ever finding out what the real cause was.

I coded up a little test to try and nail down the behavior. The results were a little bit interesting, and a little bit confusing. The test program just creates a directory ‘Temp’ under C:\, and then creates a two-level directory structure under that consisting of parent folder ‘Test1’ and child folder ‘Test2’. In each test folder it creates a text file, named respectively TestFile1.txt, and TestFile2.txt. It then waits for the user to press a key and attempts to delete the Test1 folder and all its children. If an exception is thrown it is dumped to the screen. You can see the entire listing here.

When you run the program you’ll see that it has created the test directory structure and is awaiting a keypress from you prior to removing it. This pause is so that we can mess with Explorer and try to find out what is causing the exception.

Let’s start by just opening Windows Explorer and confirming that the folders and files were created as specified. Of course, there’s no question that they were, but in a real piece of software the logic might be much more complex, and Explorer might just be the fastest way to confirm what is happening on disk, as it was for me while working on our server job.

You can see the Temp directory I created as a container for the test, and the two test directories underneath it. Test1 is highlighted in the left treeview, so the right-hand view shows the file TestFile1.txt, and the child directory Test2. In order to delete the test directory the program calls a method named DeleteTestDirectory().

static void DeleteTestDirectory()
{
    try
    {
        Console.WriteLine("Deleting test directory");
        Directory.Delete("c:\\Temp\\Test1", true);
    }
    catch (Exception ex)
    {
        DisplayError(ex);
        WaitForKey();
        return;
    }
}

If we return to the test program and press a key, the method executes, Directory.Delete is called with the recursive flag, and we see that the directories are removed as expected.

I italicized the phrase “in the left treeview” above, because I wanted to make a distinction regarding where the focus of input is in the Explorer window. In the previous test the focus was in the left-hand treeview, specifically the active highlight of directory ‘Test1’. Let’s move the focus over to the right-hand pane and try it again.

This also works as expected. Next I tried highlighting the file ‘TestFile1.txt’. When you highlight a file in Explorer it grabs the icon, size, attributes, created and modified dates, etc., and if you are using the preview pane it may grab a snapshot of the contents. For these reasons I suspected that highlighting the file might cause the problem I had seen while debugging our server job. But once again Directory.Delete worked as expected. I next moved down a level in the tree to directory ‘Test2’, and highlighted the file ‘TestFile2.txt’.

This is the exact same scenario as above, but one level lower down in the directory structure. It shouldn’t make a difference, but when you press the key to remove the structure in this case you see the error.

Of course the directory isn’t empty! That’s why I specified a recursive delete! So, what’s going on here? I’m damned if I know. Maybe someone at Microsoft does. I could probably figure out a lot more using System Internals’ file system monitor utility, but I don’t really have time to chase down Explorer’s foibles. I just want to know what can cause my software to fail, and how to prevent it.

It might seem obvious that competing file access can cause such problems, and it is. In most cases where you do this to yourself it doesn’t take a month’s effort to figure it out and fix it. But when it is a side effect of the state of a desktop component like Explorer the cause and solution can be harder to perceive. Speaking of solutions, here is an alternative to the DeleteTestDirectory() method. Let’s call it SafeDeleteTestDirectory().

static void SafeDeleteTestDirectory()
{
    try
    {
        Console.WriteLine("Deleting test directory with retry");
        Directory.Delete("c:\\Temp\\Test1", true);
    }
    catch (Exception ex)
    {
        DisplayError(ex);
        Thread.Sleep(1000);
        Console.WriteLine("Failed! Trying again...");
        try
        {
            Directory.Delete("c:\\Temp\\Test1", true);
        }
        catch (Exception ex2)
        {
            DisplayError(ex2);
            WaitForKey();
            return;
        }
    }
}

What this does is simply react to the first exception by waiting 1000 milliseconds, and then trying again. If the problem recurs we just exit. Interestingly, this works.

However it does seem to give Explorer a case of stomach cramps, which might be a clue to the difference between the scenarios that succeeded, and the one that failed.

So, should you put code like the above into all the places where you call Directory.Delete() with the recursive flag? I’d have to say yes. You might be able to argue against it in the case of a hidden folder, the idea being that if the user can’t open Explorer and see the folder they can’t drill into it and cause this issue. But I don’t think I’d rely on it, personally.

Directory.Delete() Test: program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

namespace DirectoryTest
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Creating test directory");
                CreateTestDirectory();
            }
            catch (Exception ex)
            {
                DisplayError(ex);
                WaitForKey();
                return;
            }

            Console.WriteLine("Directory created");
            WaitForKey();

            DeleteTestDirectory();

            //SafeDeleteTestDirectory();
            
            Console.WriteLine("Directory deleted");
            WaitForKey();
        }

        static void DeleteTestDirectory()
        {
            try
            {
                Console.WriteLine("Deleting test directory");
                Directory.Delete("c:\\Temp\\Test1", true);
            }
            catch (Exception ex)
            {
                DisplayError(ex);
                WaitForKey();
                return;
            }
        }

        static void SafeDeleteTestDirectory()
        {
            try
            {
                Console.WriteLine("Deleting test directory with retry");
                Directory.Delete("c:\\Temp\\Test1", true);
            }
            catch (Exception ex)
            {
                DisplayError(ex);
                Thread.Sleep(1000);
                Console.WriteLine("Failed! Trying again...");
                try
                {
                    Directory.Delete("c:\\Temp\\Test1", true);
                }
                catch (Exception ex2)
                {
                    DisplayError(ex2);
                    WaitForKey();
                    return;
                }
            }
        }
        
        static char WaitForKey()
        {
            Console.WriteLine("Press any key...");
            ConsoleKeyInfo cki = Console.ReadKey();
            return cki.KeyChar;
        }

        
        static void CreateTestDirectory()
        {
            Directory.CreateDirectory("c:\\Temp\\Test1");
            Directory.CreateDirectory("c:\\Temp\\Test1\\Test2");

            using (StreamWriter sw = new StreamWriter("c:\\Temp\\Test1\\TestFile1.txt", false))
            {
                sw.WriteLine("This is a line of text");
                sw.Close();
            }

            using (StreamWriter sw = new StreamWriter("c:\\Temp\\Test1\\Test2\\TestFile2.txt", false))
            {
                sw.WriteLine("This is a line of text");
                sw.Close();
            }
        }

        static void DisplayError(Exception ex)
        {
            Exception current = ex;
            while (null != current)
            {
                Console.WriteLine("Error: " + ex.Message);
                Console.WriteLine("Stack: " + ex.StackTrace);
                Console.WriteLine(Environment.NewLine);
                current = current.InnerException;       
            }
        }
    }
}

Our High Tech School

I was on my way home from a day kayaking in the Pine Barrens with a few friends when my daughter called on the cel phone. Would I be home soon? I told her I would be there in about an hour, and asked why she wanted to know. She told me she had to print something out for school, and needed help. I said I would help, of course, but that it was getting late and she should probably just put the thumb drive on my desk with a note about which file, and get to bed. She told me it was not that simple, because what she needed to print was a website.

Wait. What? You want to print a website? I don’t buy $75 ink reloads so that we can print websites. What website were we considering printing? Turns out it was a website that she was asked to make for a class in school. I asked whether it was a fake website, because that would make sense, if it were a Powerpoint presentation or something. Nope, a real website. It was made with webs.com, and her teacher wanted the class to print out their websites and bring them in for review. At this point I spluttered, and mumbled that we would talk when I got home, and that I shouldn’t really be discussing such silliness on the cel phone while doing 70 MPH on the turnpike.

In any case, there was nothing to discuss. We’ve been down this road before, and she gets very hard headed at the merest suggestion that I oppose something one of her teachers wants her to do, which I admit I do quite a bit, usually because it involves me spending money. These days I just grit my teeth and acquiesce. But dammit, printing a website? Allow me to humbly suggest, dear sir or madam teacher, that you are doing it wrong. If you ask my daughter to make a website, and the only way you can come up with to review the work is for me to print the whole thing out in color, then please stop trying to teach her about websites. Honestly, she knew more about websites than either of us by the time she entered your school, and I build them for a living. I’d go back to math, science, history, English, etc., because this Internet stuff is hard.

String Formatting of TimeSpans in .NET 4

We ran into something interesting the other day when one of our services crashed. The problem turned out to be an unhandled exception that was allowed to propagate up the call stack of a thread created by a third-party unmanaged DLL, but that wasn’t the interesting part. What got our attention was the source of the exception, in a line similar to this:

String.Format("blah blah {0:mm:ss:fff}", myTimeSpan);

We had just upgraded our entire platform to .NET 4.0, and suddenly a line of code that had been in production for over a year was failing. Neat! A little poking around isolated the problem, which is illustrated by the following:

class Program
{
    static void Main(string[] args)
    {
        TimeSpan ts = new TimeSpan(12, 30, 30);
        try
        {
            Console.WriteLine(Environment.Version);
            Console.WriteLine(String.Format("{0:hh:mm:ss}", ts));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.StackTrace);
        }
        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }
}

Compile that code for .NET 3.5 and it runs fine. Compile it for .NET 4.0 and it throws “Input string not in the correct format.” A hint as to why can be found in Microsoft’s MSDN page for the 4.0 version of TimeSpan:

Beginning with the .NET Framework version 4, the TimeSpan structure supports culture-sensitive formatting through the overloads of its ToString method, which converts a TimeSpan value to its string representation.

So in .NET 4 you can use overloads of TimeSpan.ToString() for culture-sensitive formatting of TimeSpan values, just as you already could with DateTime. It turns out that String.Format() now delegates to those overloads when it has to format a TimeSpan. All well and good, but why is it failing? A little further down on the same page is another clue:

In some cases, code that successfully formats TimeSpan values in .NET Framework 3.5 and earlier versions fails in .NET Framework 4. This is most common in code that calls a composite formatting method to format a TimeSpan value with a format string.

But our format string appeared to contain only valid format specifiers and delimeters. A little more digging turned up this page, describing custom TimeSpan format strings, which at the very end contained another clue:

Any other unescaped character in a format string, including a white-space character, is interpreted as a custom format specifier. In most cases, the presence of any other unescaped character results in a FormatException. There are two ways to include a literal character in a format string: enclose it in single quotation marks (the literal string delimiter); or precede it with a backslash (“\”), which is interpreted as an escape character. This means that, in C#, the format string must either be @-quoted, or the literal character must be preceded by an additional backslash.

The colon is a special character in a format string. It delimits the parameter array index value from the rest of the format, i.e. “{0:yadayada}”. If you put another colon in there, as we did to delimit minutes, seconds, and milliseconds, you must now escape it. So in .NET 4 the correct version of the test program would be:

class Program
{
    static void Main(string[] args)
    {
        TimeSpan ts = new TimeSpan(12, 30, 30);
        try
        {
            Console.WriteLine(Environment.Version);
            Console.WriteLine(String.Format(@"{0:hh\:mm\:ss}", ts));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.StackTrace);
        }
        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }
}

If you compile and run that against .NET 4 it works fine. Note the use of the @ to prevent the compiler from parsing escape characters in the format string. You could also use double backslashes.

If you have a lot of code that formats TimeSpans this could obviously be a pain. Fortunately our exposure was limited, and there is a workaround that I will describe below that will save you a ton of effort. But before that let’s take a look at DateTime, and see if it also follows the new behavior. I think we should be able to count on some consistency here. The test program modified to test a DateTime looks like this:

class Program
{
    static void Main(string[] args)
    {
        DateTime dt = DateTime.Now;

        try
        {
            Console.WriteLine(Environment.Version);
            Console.WriteLine(String.Format("{0:hh:mm:ss}", dt));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.StackTrace);
        }
        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }
}

But wait, when we run this it works fine. DateTime’s formatting methods still allow colons to appear in the format string after the first delimiter. That presents us with an important decision: if we do have a lot of code that formats TimeSpans, should we change it? Is Microsoft going to make this behavior consistent in the future? If so, which one wins? Personally, I wouldn’t change anything at this point, and the good news is that you don’t have to change any C# code to make this problem disappear.

Instead, you can add an element to the runTime section of your application configuration file, like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <runtime>
        <TimeSpan_LegacyFormatMode enabled="true" />
    </runtime>
</configuration>

I can confirm this works, and that it gives me indigestion to have to add even more crap to our already voluminous configuration files, but for some people it might make more sense than changing code, at least until Microsoft clarifies which way they are going with this.