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
C:\Windows\Temp
(for IIS or services running under LocalSystem
account)%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:
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...
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 .
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:
schtasks /Create /TR "cmd /c call DEL /F /S /Q %^TEMP%" /TN "Delete Global Temp Files" /sc WEEKLY /ST 12:00 /ru system
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With