System.IO.IOException: "The file exists" when using System.IO.Path.GetTempFileName() - resolutions?

One of my customers got an exception whenever he tried to use my product. I obtained the callstack of the exception that had occurred, the top of which is:

at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.__Error.WinIOError()
   at System.IO.Path.GetTempFileName()
   at System.Windows.Input.Cursor.LoadFromStream(Stream cursorStream)
   at System.Windows.Input.Cursor..ctor(Stream cursorStream)

Googling this, I found plenty of blog posts stating this exception is thrown when there are more than 65535 temp files in the %TEMP% folder, and that the solution is to simply clear out the old temp files. I can ask the customer to do that, but this might only be a temporary solution - what if they are regularly running some other piece of software that makes frequent calls to GetTempFileName, which will make the problem reoccur over and over?

I can't just programmatically clear out the %TEMP% folder, as that might somehow damage something else, and I can't avoid calling GetTempFileName (and using my own temp folder instead) as it's not me but WPF code that's calling it.

Is there any permanent solution for this?

UPDATE: I've confirmed that the problem where the %TEMP% folder is overflowing with log files is not caused by my own code, and must be caused by some other 3rd party application on the customer's machine. I also looked into the implementation of Cursor.LoadFromStream and it surely isn't at fault - it generates a temp file, but then deletes it in finally block.


If this is happening to you on a production environment or with an app that you can't change, the quick fix is to empty the Temp folder.

Depending on the user that is running the application you should either

  • Empty C:\Windows\Temp (for IIS or services running under LocalSystem account)
  • Or %temp% for locally logged on users (which for me is C:\Users\MyUserName\AppData\Local\Temp).

On the other side, if your own code is throwing this, and you want to prevent this from happening ever again:

  1. Do not use System.IO.Path.GetTempFileName()!

GetTempFileName() is a wrapper of the two decades old Win32 Api. It generate file names that will very easily collide. It circumvents those collitions by heavily looping on the file system, iterating possible file names from "%temp%\tmp0000.tmp" to "tmpFFFF.tmp" and skipping already existing ones. This is a I/O intensive, slow, and frankly terrible algorithm. Also using only 4 hex characters is what makes the artificial limit of 65536 files before failing.

The alternative is to generate file names that will not collide. For example, lets reuse GUID's logic: 32 hex digits will almost never collide.

private string GetTempFileName()
{
    return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
}
// Sample: c:\Windows\Temp\2e38fe87-f6bb-4b0d-90b3-2d07016324c1

This expands the limit from 65k to 4k millions files max (theoretically)... Of course, having leaked 65k files is already terrible, so...

  1. Do not leak temp files!

Double check your app for all happy and unhappy paths (like unexpected exceptions). Ensure it's correctly disposing each FileStream and deleting the temp files in Finally blocks .

  1. Clean the temp folder

Clean it now, and educate the system administrator to clean it periodically, because you can't trust every app in the wild. On my own servers I would automate this task using:

  • For global Windows\Temp

schtasks /Create /TR "cmd /c call DEL /F /S /Q %^TEMP%" /TN "Delete Global Temp Files" /sc WEEKLY /ST 12:00 /ru system

  • For current user:

schtasks /Create /TR "cmd /c call DEL /F /S /Q %^TEMP%" /TN "Delete %username% Temp Files" /sc WEEKLY /ST 12:00


As I mentioned in my last comment I think your only safe way to do this is to ask the user if they want you to delete files and try again. It is imperative that you get the users input into this, this way it is at their own peril. In my head its something similar to.

public Stream GetStream(Stream cursorStream)
{
    try
    {
       //getting stream
    }
    catch(IOE)
    {
        MessageBox.Show(this, "Unable to get stream, your temporary
                              folder may be full, do you want to try deleting 
                                some and try again?");
         if(yes)
         try
         {
             //delete and try again
             return GetStream(cursorStream);
         }
         catch(IOE)
          {
                //no luck
           }
          else
              return null;
    }

}

An optional check to make sure could be,

Directory.EnumerateFiles(Path.GetTempPath(), "*", SearchOption.TopLevelOnly)
  .Count() == ushort.MaxValue;

Here's the code I used in the end, and put early in my app's initialization code-path, before any calls to Cursor.LoadFromStream might occur:

    private void WarnUserIfTempFolderFull()
    {
        string tempFile = null;
        try
        {
            tempFile = Path.GetTempFileName();
        }
        catch (IOException e)
        {
            string problem = "The Temporary Folder is full.";

            string message = "{ProductName} has detected that the Windows Temporary Folder is full. \n" + 
                             "This may prevent the {ProductName} from functioning correctly.\n" + 
                             "Please delete old files in your temporary folder (%TEMP%) and try again.";

            Logger.Warn(problem);

            MessageBox.Show(message, caption: problem);
        }
        finally
        {
            if (tempFile != null) File.Delete(tempFile);
        }
    }

For anyone else who has experienced this problem and can't find any overflowing temp-folder - Check the "C:/Windows/Temp"-folder. Cleaning this folder solved my problems.


Solutions:

  1. The right one. Detect which application is producing so many temporary files and not deleting them. Utilities like Process monitor should help you. Then either fix the application or throw it away. And yes, this might be your application. that's why I'd recommend you to detect the source of evil.
  2. The easiest one. Use your own temporary directory. This won't help if the files are being created from your code.
  3. The ugliest one. Clear the temporary directory from your application. You're absolutely right about the consequences - you could break another application.