System.IO.IOException: "El archivo existe" cuando se utiliza System.IO.Path.GetTempFileName () - ¿resoluciones?

84

Uno de mis clientes tenía una excepción cada vez que intentaba utilizar mi producto. Obtuve la pila de llamadas de la excepción que se había producido, la parte superior de la cual es:

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)

Al buscar en Google, encontré muchas publicaciones de blog que indican que esta excepción se produce cuando hay más de 65535 archivos temporales en la carpeta% TEMP%, y que la solución es simplemente borrar los archivos temporales antiguos. Puedo pedirle al cliente que haga eso, pero esto podría ser solo una solución temporal: ¿qué pasa si ejecutan regularmente algún otro software que realiza llamadas frecuentes a GetTempFileName, lo que hará que el problema vuelva a ocurrir una y otra vez?

No puedo simplemente borrar programáticamente la carpeta% TEMP%, ya que eso de alguna manera podría dañar algo más, y no puedo evitar llamar a GetTempFileName (y usar mi propia carpeta temporal en su lugar) ya que no soy yo, sino el código WPF el que lo llama.

¿Existe alguna solución permanente para esto?

ACTUALIZACIÓN : He confirmado que el problema en el que la carpeta% TEMP% se desborda con archivos de registro no es causado por mi propio código, y debe ser causado por alguna otra aplicación de terceros en la máquina del cliente. También examiné la implementación de Cursor.LoadFromStreamy seguramente no tiene la culpa: genera un archivo temporal, pero luego lo elimina en el finallybloque.

Omer Raviv
fuente
3
Podría crear su propia carpeta "Temp" que se elimine (en los datos de la aplicación ") pero probablemente sería un ballache para cambiar todas las referencias, buena pregunta
Sayse
La pregunta no está relacionada con WPF, etiqueta eliminada. Además, ¿por qué no arreglar el código, que produce tantos archivos temporales sin borrar?
Dennis
1
@ Digo que no puedo hacer eso porque es WPF el Cursor.LoadFromStreamque genera el archivo temporal. @Dennis Está relacionado con la Cursor.LoadFromStreamclase de WPF . Es posible que el código ofensivo que produce tantos archivos temporales sin eliminarlos ni siquiera sea el mío, y aún necesitaría abordar la excepción.
Omer Raviv
¿Puedes averiguar qué aplicación está dejando atrás todos estos archivos temporales? ¿Es tu aplicación? Si WPF está creando estos archivos temporales, ¿ha confirmado que los está eliminando cuando ya no son necesarios?
Ashigore
2
@OmerRaviv Creo que su única opción es intentar / capturar el IOE y preguntarle al usuario si quiere que elimine los archivos temporales y vuelva a intentarlo
Sayse

Respuestas:

15

Como mencioné en mi último comentario, creo que la única forma segura de hacerlo es preguntarle al usuario si quiere que elimine archivos y vuelva a intentarlo. Es imperativo que obtenga la participación de los usuarios en esto, de esta manera es bajo su propio riesgo. En mi cabeza es algo similar a.

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;
    }

}

Una verificación opcional para asegurarse podría ser,

Directory.EnumerateFiles(Path.GetTempPath(), "*", SearchOption.TopLevelOnly)
  .Count() == ushort.MaxValue;
Sayse
fuente
2
Iré con un try / catch como sugirió. La prueba booleana que sugirió es realmente incorrecta; es probable que haya otros archivos en la carpeta% TEMP%, además de los que tienen el formato "tmpXXXX.tmp" que usa GetTempFileName (), por lo que su prueba puede volver a ser verdadera cuando en realidad no hay ningún problema y falso cuando HAY un problema.
Omer Raviv
La prueba fue diseñada para encontrar todos los archivos en una carpeta temporal (aunque admito que puede que no sea la carpeta temporal correcta) y ve si eso es igual a 65535, "*"encontrará todos los archivos (independientemente de si tienen una extensión o no) espero que ayude
Sayse
Puede eliminar todos los archivos anteriores a, digamos, un día. Es poco probable que otra aplicación utilice un archivo temporal de más de un día.
JT Taylor
2
Solo otro punto en la verificación opcional. Cuando encontré este problema en un servidor de compilación, el directorio temporal tenía más de 65535 archivos. Ese recuento solo funciona si lo ÚNICO que crea archivos temporales es a través de la clase auxiliar Path.
rshadman
36

Si esto le sucede en un entorno de producción o con una aplicación que no puede cambiar, la solución rápida es vaciar la carpeta Temp.

Dependiendo del usuario que esté ejecutando la aplicación, debería

  • Vacío C:\Windows\Temp(para IIS o servicios que se ejecutan en la LocalSystemcuenta)
  • O %temp%para los usuarios que han iniciado sesión localmente (que para mí es C:\Users\MyUserName\AppData\Local\Temp).

Por otro lado, si su propio código arroja esto y desea evitar que esto vuelva a suceder nunca más:

  1. ¡No use System.IO.Path.GetTempFileName ()!

GetTempFileName()es un contenedor de la API Win32 de hace dos décadas . Genera nombres de archivos que colisionarán muy fácilmente. Elude esos collitions por bucle en gran medida en el sistema de archivos, la iteración de posibles nombres de los archivos de "%temp%\tmp0000.tmp"a "tmpFFFF.tmp"y saltar las ya existentes. Este es un algoritmo de E / S intensivo, lento y francamente terrible. Además, el uso de solo 4 caracteres hexadecimales es lo que hace que el límite artificial de 65536 archivos antes de fallar.

La alternativa es generar nombres de archivos que no colisionen. Por ejemplo, reutilicemos la GUID'slógica: 32 dígitos hexadecimales casi nunca colisionarán.

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

Esto expande el límite de 65k a 4k millones de archivos como máximo (teóricamente) ... Por supuesto, haber filtrado 65k archivos ya es terrible, así que ...

  1. ¡No filtre archivos temporales!

Revise su aplicación para ver todos los caminos felices e infelices (como excepciones inesperadas). Asegúrese de que esté desechando correctamente cada FileStream y eliminando los archivos temporales en los bloques Finalmente.

  1. Limpiar la carpeta temporal

Límpielo ahora y eduque al administrador del sistema para que lo limpie periódicamente, porque no puede confiar en todas las aplicaciones en la naturaleza. En mis propios servidores, automatizaría esta tarea usando:

  • Para Windows \ Temp global

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

  • Para el usuario actual:

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

Gerardo Grignoli
fuente
5

Aquí está el código que usé al final, y puse temprano en la ruta del código de inicialización de mi aplicación, antes de Cursor.LoadFromStreamque ocurra cualquier llamada a :

    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);
        }
    }
Omer Raviv
fuente
2

Soluciones:

  1. Lo que es correcto. Detecta qué aplicación está produciendo tantos archivos temporales y no los elimina. Las utilidades como Process monitordeberían ayudarte. Luego, arregle la aplicación o deséchela. Y sí, esta podría ser tu aplicación. por eso te recomiendo que detectes la fuente del mal.
  2. El más sencillo. Utilice su propio directorio temporal. Esto no ayudará si los archivos se crean a partir de su código.
  3. El más feo. Borre el directorio temporal de su aplicación. Tiene toda la razón sobre las consecuencias: podría romper otra aplicación.
Dennis
fuente
Usar su propio directorio temporal no es necesariamente una solución. Utilizo la API para generar un nombre de archivo temporal pero lo escribo en mi propio directorio. Desafortunadamente, incluso ESO falla.
George Mauer
2
// one more implementation
string GetTempFileName()
{
    return Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
}
volody
fuente
1

Como sugirió Sayse , puede intentar configurar la variable de entorno% TEMP% cuando se inicie su aplicación.

Environment.SetEnvironmentVariable("TEMP", "<dir>");
Capilla Ed
fuente
Esa es una gran idea, pero desafortunadamente mi aplicación es una extensión de Visual Studio que debe coexistir pacíficamente con otras extensiones, y me temo que esto podría de alguna manera dañar inadvertidamente el comportamiento de las otras extensiones.
Omer Raviv
Esta solución no ayudará si es su propio programa el que está dejando atrás todos estos archivos. El nuevo directorio simplemente se llenará y es malo eliminar arbitrariamente archivos de una carpeta cuando no tienes idea de para qué sirven, incluso si es una carpeta específica de tu aplicación.
Ashigore
@Ashigore Sí, obviamente esto no solucionará un error que ha creado. Específicamente haciendo referencia what if they are regularly running some other piece of software that makes frequent calls to GetTempFileName.
Ed Chapel
@OmerRaviv Esta información es útil. De hecho, esto no funcionará en este escenario.
Ed Chapel
1
Lo siento @EdChapel, SO ha bloqueado mi voto, no puedo eliminarlo a menos que se edite la respuesta.
Gerardo Grignoli
1

Para cualquier otra persona que haya experimentado este problema y no pueda encontrar ninguna carpeta temporal desbordada, consulte la carpeta "C: / Windows / Temp". Limpiar esta carpeta resolvió mis problemas.

Fredrik Fahlman
fuente