C++/Win32: Comment attendre une attente de suppression pour terminer?
Résolu:
* Solution: @sbi
* Explication de ce qui se passe réellement: @Hans
* Explication de pourquoi OpenFile ne passe pas à travers "en ATTENTE de SUPPRESSION": @Benjamin
Le Problème:
Notre logiciel est en grande partie un interprète moteur pour un propriétaire langage de script. Que langage de script a la possibilité de créer un fichier, un processus, puis supprimez le fichier. Ce sont toutes des opérations distinctes, et pas de descripteurs de fichiers sont maintenues ouvertes entre ces opérations. (c'est à dire pendant le fichier de créer un handle est créé, utilisés pour l'écriture, puis fermé. Pendant le traitement du fichier de la partie, un descripteur de fichier ouvre le fichier, lit, et qu'il est fermé à l'EOF. et Enfin, supprimer les usages ::DeleteFile qui a seulement l'utilisation d'un nom de fichier, pas un descripteur de fichier à tous).
Récemment, nous avons réalisé que l'un particulier de la macro (script) échoue parfois à être en mesure de créer le fichier au hasard moment ultérieur (c'est à dire qu'il réussit au cours de la première centaine d'itérations de "créer, de traiter, de les supprimer", mais quand il revient à la création d'une centaine de et des première fois, Windows réponses "Accès Refusé").
Chercher à approfondir la question, j'ai écrit un programme très simple qui passe en boucle sur quelque chose comme ceci:
while (true) {
HANDLE hFile = CreateFileA(pszFilename, FILE_ALL_ACCESS, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return OpenFailed;
const DWORD dwWrite = strlen(pszFilename);
DWORD dwWritten;
if (!WriteFile(hFile, pszFilename, dwWrite, &dwWritten, NULL) || dwWritten != dwWrite)
return WriteFailed;
if (!CloseHandle(hFile))
return CloseFailed;
if (!DeleteFileA(pszFilename))
return DeleteFailed;
}
Comme vous pouvez le voir, c'est direct à l'API Win32, et vachement simple. J'ai créer un fichier, écrire, fermez la poignée, le supprimer, rincer, répéter...
Mais quelque part le long de la ligne, je vais obtenir un Accès Refusé (5) erreur lors de la CreateFile() de l'appel. En regardant de sysinternals de ProcessMonitor, je peux voir que la question sous-jacente est qu'il y a une attente supprimer dans le fichier alors que je suis en train de le créer encore une fois.
Questions:
* Est-il possible d'attendre pour que la suppression complète?
* Est-il un moyen de détecter qu'un fichier est en attente de suppression?
Nous avons essayé la première option, il suffit de la WaitForSingleObject() sur le HFILE. Mais le HFILE est toujours fermé avant la WaitForSingleObject s'exécute, et ainsi de WaitForSingleObject retourne toujours WAIT_FAILED. Clairement, en essayant d'attendre la fermeture de la poignée ne fonctionne pas.
Je pouvais attendre une notification de changement pour le dossier que le fichier existe dans. Toutefois, cela semble être une très généraux-intensif quelque chose à ce qui est un problème, seulement de temps en temps (à savoir: dans mes tests sur mon Win7 x64 E6600 PC, il échoue généralement à l'itération 12000+ -- sur d'autres machines, il peut se produire dès que l'itération 7 ou 15 ou 56 ou jamais).
J'ai été incapable de discerner un CreateFile() arguments qui permettrait explicitement de cet éther. Peu importe ce que les arguments CreateFile a, c'est vraiment pas d'accord avec l'ouverture d'un fichier pour tout accès lorsque le fichier est en attente de suppression. Et depuis je ne peux voir ce comportement à la fois sur la boîte de XP et sur un Win7 x64 boîte, je suis tout à fait certain que c'est de base NTFS comportement "comme prévu" par Microsoft. J'ai donc besoin d'une solution qui permet à l'OS pour compléter le supprimer avant d'essayer de procéder, de préférence w/o lier des cycles CPU inutilement, et sans l'extrême surcharge de regarder le dossier que ce fichier est en cours (si possible).
Merci de prendre le temps de lire ceci et poste une réponse. Des Questions de clarification bienvenue!
[1] Oui, cette boucle de retour sur un échec d'écriture ou d'un échec de la fermeture qui fuit, mais puisque c'est une simple console application de test, l'application se termine, et Windows garantit que toutes les poignées sont fermés par le système d'exploitation lorsqu'un processus se termine. Donc pas de fuites existent pas ici.
bool DeleteFileNowA(const char * pszFilename)
{
//determine the path in which to store the temp filename
char szPath[MAX_PATH];
strcpy(szPath, pszFilename);
PathRemoveFileSpecA(szPath);
//generate a guaranteed to be unique temporary filename to house the pending delete
char szTempName[MAX_PATH];
if (!GetTempFileNameA(szPath, ".xX", 0, szTempName))
return false;
//move the real file to the dummy filename
if (!MoveFileExA(pszFilename, szTempName, MOVEFILE_REPLACE_EXISTING))
return false;
//queue the deletion (the OS will delete it when all handles (ours or other processes) close)
if (!DeleteFileA(szTempName))
return false;
return true;
}
- Vous assurer que toutes les poignées sont fermées? Parce que ce que vous avez écrit est exactement décrit sur le site MSDN: "Si vous appelez CreateFile sur un fichier qui est en attente de suppression suite à un précédent appel à DeleteFile, la fonction échoue. Le système d'exploitation retards, suppression de fichiers jusqu'à ce que toutes les poignées vers le fichier est fermé. GetLastError renvoie ERROR_ACCESS_DENIED."
- J'ai créé une application console qui fait le code ci-dessus dans une boucle. Il échoue finalement... généralement autour de l'itération 12000-15000. Donc, sauf si vous voyez quelque façon que ci-dessus 8 lignes de code fuites d'une poignée, je pense que c'est tout à fait impossible (au moins pour ce test de l'applet).
- Pour être plus humble à ce sujet: c'était notre hypothèse de départ ainsi. Mais après la chasse aux fantômes pendant des jours, je l'ai simplifié au-dessus, et comme je l'ai dit, les boucles qui présente le problème de manière cohérente (si au hasard).
- merci - mais la dernière est fermée. C'est le problème: Windows est de le traiter comme s'il n'était pas fermé quand il est. J'ai besoin d'un moyen de synchroniser avec le système d'exploitation pour s'assurer que mon code attend pour la suppression complète.
- Dans votre exemple de programme, si WriteFile échoue ou s'il était seulement une partie de l'écriture pour une raison quelconque, vous ne le pourrez pas fermer le handle de fichier. Alors peut-être vérifier il n'y a pas de court-circuit des rendements qui pourraient fuir le descripteur de fichier. À l'aide d'un garde de la classe qui ferme la poignée dans le dtor est l'approche la plus sûre.
- Oui, dans un non-mannequin-exemple-code-clip, je garde à l'encontre de toutes les issues possibles à l'aide de RAII. Toutefois, pour que cette super-simple exemple, qui, tout simplement, quitte la boucle lors de toute situation d'échec se présente, je suis garanti que la poignée est fermé. Et la boucle n'est jamais essayer de continuer avec une fuite HFILE.
- J'ai déjà vu cela et a mon commentaire supprimé.
- Je me demande si il y a une sorte de sync() appel pour windows. Une recherche rapide a échoué à son tour, mais il peut être intéressant de regarder pour...
- Intéressant et bien documenté question, +1. BTW, l'équivalent de la fonction de synchronisation de Windows doit être le FlushFileBuffers API.
- Hans explication sonne comme il frappe le clou sur la tête. Il n'a pas l'air plausible pour moi que le système de fichiers lui-même pourrait être confus au sujet de son état interne, c'est à dire qu'il pourrait le croire en un fichier supprimé existait encore, seulement parce qu'il n'avait pas encore terminé l'ensemble de l'asynchrone de ménage associé à la suppression de ce fichier.
- Ce qui est vraiment pose la question ici est, pourquoi ne pas l'OS de faire ce que je fais ci-dessus (permettre à un autre fichier qui sera créé par le même nom, tandis que l'ancien est toujours en attente de suppression). Il ne semble pas comme il n'y a aucune bonne raison de ne pas...
- J'ai eu ce problème causé par le service Recherche Windows, il aurait remarqué, j'ai fait un répertoire et de le verrouiller à l'index, à la même époque, j'ai tenté de le supprimer et a reçu l'erreur 5. Il est facile de reproduire ce par la création et suppression de répertoires/fichiers dans une boucle avec l'indexeur activé.
- Il a été
MsMpEng
dans mon cas, qui a bloqué le fichier. J'ai découvert MS confirmation que l'indice/antivirus SW peut bloquer le fichier. Maintenant, j'ai donc l'habitude de supprimer dans la boucle de nouvelle tentative ou mieux créer un nouveau fichier temp au lieu de réutiliser seul nom, avec l'espoir que la seule suppression sera effective en fin de compte. Maintenant, j'ai un problème similaire.Gcc p2
échoue dansgcc p.c > p.exe && p && gcc p2.c > p.exe && p ...
parce que juste terminée p.exe est bloqué et rien dans filemon, mais p processus accède au fichier.
Vous devez vous connecter pour publier un commentaire.
Pourquoi ne pas d'abord renommer le fichier à supprimer, puis de le supprimer?
Utilisation
GetTempFileName()
pour obtenir un nom unique, puis utilisezMoveFile()
pour renommer le fichier. Puis supprimer le fichier renommé. Si la suppression est en effet asynchrone et pourrait entrer en conflit avec la création d'un même fichier (comme vos tests semble l'indiquer), ce qui devrait résoudre le problème.Edit: bien sûr, si votre analyse est juste et les opérations de fichier se produire un peu asynchrone, cela pourrait présenter le problème que vous tentez de supprimer le fichier avant que le changement n'est nécessaire. Mais alors vous pouvez toujours continuer à essayer de supprimer dans un thread d'arrière-plan.
Edit #2: Si Hans est bon (et je suis enclin à croire à son analyse), puis en déplaçant peut pas vraiment aider, parce que vous pourriez ne pas être en mesure de renommer un fichier qui est ouvert par un autre processus. (Mais vous pourriez alors, je n'en sais rien.) Si c'est effectivement le cas, la seule façon que je peux trouver est de "continuer à essayer". Vous devrez attendre quelques millisecondes et réessayer. Gardez un délai d'attente d'abandonner, quand ce n'aide pas.
Il existe d'autres procédés de Windows qui veulent un morceau de ce fichier. L'indexeur de recherche est un candidat évident. Ou un scanner de virus. Ils vont ouvrir le fichier pour le partage complet, y compris FILE_SHARE_DELETE, afin que les autres processus ne sont pas fortement affectée par eux de l'ouverture du fichier.
Qui fonctionne généralement bien, à moins de créer/écrire/supprimer à un taux élevé. La suppression de la volonté de réussir, mais le fichier ne peut pas disparaître du système de fichiers jusqu'à la dernière poignée est fermé. La poignée tenue par, disons, l'indexeur de recherche. Tout programme qui essaie d'ouvrir qu'en attendant-supprimer le fichier sera frappé par erreur 5.
Cela, c'est un problème générique sur un système d'exploitation multitâche, vous ne pouvez pas savoir ce que les autres processus pourrait vouloir salir avec vos fichiers. Votre modèle d'utilisation semble inhabituel, d'examiner en premier. Une solution consisterait à intercepter l'erreur, le sommeil et essayez de nouveau. Ou de déplacer le fichier dans la corbeille avec SHFileOperation().
Idiot suggestion - puisqu'il échoue rarement, que diriez-vous de simplement attendre quelques millisecondes en cas d'échec et essayer de nouveau? Ou, si la latence est importante, passez à un autre nom de fichier, laissant l'ancien fichier sera supprimé plus tard.
Utilisation GetFileInformationByHandleEx fonction avec FILE_STANDARD_INFO structure.
Mais la fonction ne peut pas résoudre votre problème. @sbi de la solution.
Ce peut-être pas votre problème particulier, mais il est possible aussi, je vous suggère de sortir Le Moniteur De Processus (Sysinternals) et de voir.
J'ai eu exactement le même problème et a découvert que Comodo Internet Security (
cmdagent.exe
) a été de contribuer au problème. Auparavant, j'avais un dual-core de la machine, mais quand je l'ai mis à niveau vers un processeur Intel i7, soudain, mon travail logiciel (jam.exe
par Perfore logiciel) ne fonctionnait plus parce que c'était le même schéma (a supprimer puis de créer, mais pas de chèque). Après de débogage, le problème que j'ai trouvé GetLastError() retournait accès refusé, mais le Moniteur de Processus révèle un "en attente de suppression'. Ici, c'est la trace:Comme vous pouvez le voir, il ya une demande pour supprimer suivie par plusieurs tentatives pour ouvrir à nouveau le fichier en
jam.exe
(c'est unfopen
dans une boucle). Vous pouvez voircmdagent.exe
sans doute avait ouvert le fichier qu'il ferme sa poignée et puis tout à coupjam.exe
est en mesure de maintenant, ouvrez le fichier.Bien sûr, la solution proposée à attendre et essayer de nouveau, et il fonctionne très bien.
Puisque vous êtes en train de créer un nouveau fichier, le traitement, puis de le supprimer, il semble que vous n'avez pas vraiment soins sur ce que le nom de fichier est. Si c'est vraiment le cas, vous devriez envisager de créer un fichier temporaire. De cette façon, à chaque fois à travers le processus, vous n'avez pas à soins que le fichier n'a pas encore été supprimé.
J'ai effectivement eu le même problème pendant l'utilisation de la fonction LoadLibrary(chemin) je ne pouvais pas supprimer le fichier dans chemin.
La solution était de "fermer la poignée" ou utilisez le FreeLibrary(chemin) méthode.
REMARQUE: Veuillez lire la rubrique "commentaires" sur MSDN concernant la FreeLibrary().
Si CreateFile retourne INVALID_HANDLE_VALUE ensuite, vous devez déterminer ce que GetLastError retourne dans votre situation particulière (en attente de suppression) et la boucle de retour à CreateFile basée sur cette erreur de code seulement.
Modifier
Serait le FILE_FLAG_DELETE_ON_CLOSE drapeau de vous acheter quelque chose?
GetLastError()
retourne.Je suis peut être en retard à la fête, mais sous Vista/Win7, il est DeleteFileTransacted, qui supprime les fichier à l'aide de transactions qui garantit qu'ils sont supprimés (bouffées de chaleur tampons de fichier, etc.). Pour la compatibilité XP ce n'est pas une option si.
Une autre idée de la façon dont cela pourrait être fait est de OpenFile avec drapeau OF_CREATE qui définit la longueur de zéro si le fichier existe ou qu'il la crée si elle ne l'est pas et puis d'appeler FlushFileBuffers sur le descripteur de fichier à attendre pour cette opération (prise de fichier de longueur nulle) pour valider. À la fin du fichier est de taille 0 et puis il suffit d'appeler DeleteFile.
Vous pouvez ensuite tester si le fichier existe ou si elle est de longueur nulle à traiter de la même manière.
Selon [1] vous pouvez utiliser
NtDeleteFile
pour éviter de la nature asynchrone de DeleteFile. Aussi [1] donne quelques détails sur la façon dont DeleteFile œuvres.Malheureusement la documentation officielle sur
NtDeleteFile
[2] ne pas mentionner aucun détail particulier sur cette question.[1]
http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FFile%2FNtDeleteFile.html
[2]
https://msdn.microsoft.com/en-us/library/windows/hardware/ff566435(v=vs. 85).aspx
NtDeleteFile
seulement, le réglage de la suppression de la disposition plus efficace en sautant l'étape deuxNtOpenFile
,NtSetInformationFile
séquence et ne nécessitant pas le gestionnaire d'e/S allouer un véritable objet de Fichier. Bien sûr, il a encore d'allouer IRP pour la demande d'information et d'appeler le système de fichiers du pilote, et bien sûr, le fichier ne fonctionne toujours pas dissocier, jusqu'à ce que le dernier Fichier de référence est fermé/nettoyé.La meilleure réponse a été donnée par le sbi, mais dans l'intérêt de l'exhaustivité, certaines personnes veulent aussi en savoir plus sur une nouvelle façon maintenant disponible à partir de Windows 10 RS1/1603. Il implique l'appel de la SetFileInformationByHandle de l'API de la classe FileDispositionInfoEx, et définissant des indicateurs
FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS
. Voir la réponse par RbMm.Je pense que c'est seulement par une mauvaise conception du système de fichiers. J'ai vu le même problème quand j'ai travaillé avec les ports de communication, d'ouverture/fermeture.
Malheureusement, je pense que la solution la plus simple serait de simplement réessayer pour créer le fichier d'un certain nombre de fois, si vous obtenez une
INVALID_HANDLE_VALUE
.GetLastError()
peut aussi vous donner une meilleure façon de détecter ce particulierINVALID_HANDLE_VALUE
.J'aurais préféré overlapped I/O, mais il y
CloseHandle()
etDeleteFile()
ne pas gérer les opérations qui se chevauchent 🙁