Long story short: у нас в CodeJam среди кучи других полезняшек есть микро-хелпер для работы с временными файлами: завёл-поработал-оноавтоматомгрохнулось.
Выглядит как-то так:
using (var tempFile = TempData.CreateFile())
{
DoSomethingWith(tempFile.Path);
}
// file is deleted for now
и не задал нехороший вопрос: а что нужно делать, если временный файл удалить не получается? С контрольным аргументом: куча последовательных вызовов выкушает всё место и наступит полная печаль
Текущее поведение вроде как соответствует FDG (ага, нет явных доводов за любую позицию — обращайся к FDG) и проглатывает исключения, связанные с ошибками удаления файла. Молча проглатывает. Почему так — см вот эту
кучу цитат. Если коротко — сигналить "всё пропало, шеф" при диспозе не очень хорошо, т.к. это может привести к ещё худшим последствиям.
И теперь собственно вопрос: а как быть-то? Основные варианты собраны вот тут, кому лень открывать —
скопировал
public class TempDataUseCases
{
public void CaseA_NoHelpers()
{
// weird one.var fileName = "1.txt";
Exception deletedEx = null;
try
{
File.Create(fileName).Close();
Process(fileName);
}
finally
{
try
{
File.Delete(fileName); // we do expect this will throw.
}
catch (Exception ex)
{
deletedEx = ex;
}
}
if (deletedEx != null)
{
HandleDeleteFailure(fileName, deletedEx);
}
}
public void CaseB_LetItCrash()
{
// ok if failure is handled by callerusing (var tempFile = TempData.CreateFile(throwOnDisposeFailure: true))
{
Process(tempFile.FileName);
} // dispose will throw
}
public void CaseC_LetItCrashHandle()
{
// BAD BAD BAD: we need to store fileName somewhere.string fileName = null;
try
{
using (var tempFile = TempData.CreateFile(throwOnDisposeFailure: true))
{
fileName = tempFile.FileName;
Process(tempFile.FileName);
}
}
catch (Exception ex) when (fileName != null) // HACK. Will not work on C#5 or below.
{
HandleDeleteFailure(fileName, ex);
}
}
public void CaseD_TryDelete()
{
using (var tempFile = TempData.CreateFile())
{
Process(tempFile.FileName);
if (!tempFile.TryClose())
{
HandleDeleteFailure(tempFile.FileName, null); // no exception info available.
}
}
}
public void CaseE_EnsureDeleted()
{
// kinda okusing (var tempFile = TempData.CreateFile())
{
Process(tempFile.FileName);
try
{
tempFile.EnsureDelete();
}
catch (Exception ex)
{
HandleDeleteFailure(tempFile.FileName, ex);
}
}
}
public void CaseF_ManualDelete()
{
// kinda okusing (var tempFile = TempData.CreateFile())
{
Process(tempFile.FileName);
try
{
File.Delete(tempFile.FileName);
}
catch (Exception ex)
{
HandleDeleteFailure(tempFile.FileName, ex);
}
}
}
public void CaseG_Fallback()
{
// The best?using (var tempFile = TempData.CreateFile(
(f, ex) => HandleDeleteFailure(f.FileName, ex)))
{
Process(tempFile.FileName);
}
}
private void Process(string tempFile) { }
private void HandleDeleteFailure(string fileName, Exception exception) { }
}
// Stub implementation. Unusable by design.public class TempData : IDisposable
{
public static TempData CreateFile() => new TempData();
public static TempData CreateFile(bool throwOnDisposeFailure) => new TempData();
public static TempData CreateFile(Action<TempData, Exception> deleteFallback) => new TempData();
public string FileName => null;
public void EnsureDelete()
{
throw new IOException("Cannot delete the file.");
}
public bool TryClose() => false;
public void Dispose()
{
// ok or throw
}
}
Варианты и предложения приветствуются
UPD. И да, FileOptions.DeleteOnClose подходит не всегда. Чтоб не спорить — у нас точно такой же хелпер есть для директорий. Что с ними делать будем? ; )
Если проблема архитектурная, то должно быть где-то некое GC, удаляющее старые временные файлы. Так делается в хороших персистент кьюзах.
Если проблема техническая, то очень хорошо помогает AppDomain.CurrentDomain.DomainUnload, правда доступно без приседаний только в ASP.NET под IIS и в unit-тестах. В core его к сожалению нет.
На старых windows всё ещё хорошо работает удаление при перезагрузке.
Здравствуйте, VladCore, Вы писали:
VC>Если проблема архитектурная, то должно быть где-то некое GC, удаляющее старые временные файлы. Так делается в хороших персистент кьюзах. VC>Если проблема техническая, то очень хорошо помогает AppDomain.CurrentDomain.DomainUnload, правда доступно без приседаний только в ASP.NET под IIS и в unit-тестах. В core его к сожалению нет.
Ну вот у меня примерно такое же мнение: тут надо писать свой код, опционально — в виде сервиса или простой задачи в шедулере. Т.е. уборка мусора в случае it happens — не ответственность TempFile. И текущее поведение: если не удаляется, то ничего не делаем — в принципе правильное.
Не факт, что моё мнение — единственно верное, поэтому я с радостью заслушаю все предложения.
Здравствуйте, #John, Вы писали:
J>Если временный файл не удаляется, закрываем на него все наши хендлеры и пусть ОС решает когда его удалять.
Это далеко не всегда возможно. К примеру, если файл требуется скормить чужому софту, который не умеет в FileShare.
Если такого не требуется, то есть TempData.CreateFileStream(), который открывает поток с FileOptions.DeleteOnClose.
Ок, раз второй человек настаивает на DeleteOnClose — забыли про файлы, берём такой же хелпер, но с перламутровыми пуговицами для директорий. С ним что делать?
S>а что нужно делать, если временный файл удалить не получается?
Побуду немного К.О.
а) а что может сделать пользователь библиотеки, если она вдруг выкинет эксепш?
б) рассчитывает ли кто-нибудь в реальности что такая фигня может случиться?
Лично я встречался с такой фигней на практике. Админы что-то перемудрили с настройками домена и roaming profiles, и файлы тупо не удалялись первые секунд 5.
Код во первых не был рассчитан на то что там вылетит какой-то эксепшн, а во вторых никакого варианта кроме как проигнорить это небыло.
Чего я точно не хочу и как пользователь софта и как разработчик, так это чтобы из-за каких-то ****-админов софт падал или говорил вскую непонятную хрень пользователям.
Поэтому текущее поведение — почти правильное. Я бы дополнительно вывел в трейс эксепшн и добавил файр эвента аля UnexpectedException, кому надо запишут в лог
Здравствуйте, rm822, Вы писали:
R>Поэтому текущее поведение — почти правильное. Я бы дополнительно вывел в трейс эксепшн и добавил файр эвента аля UnexpectedException, кому надо запишут в лог
Принято!
Как это дело оформлять будем? Через static-событие, делегатом в конструкторе TempFile, простым дампом в Trace или ещё как?
Как вариант, можно завести доп. источник для всей библиотеки (по аналогии с PresentationTraceSources в WPF) и писать туда.
Здравствуйте, Sinix, Вы писали:
S>Ок, раз второй человек настаивает на DeleteOnClose — забыли про файлы, берём такой же хелпер, но с перламутровыми пуговицами для директорий. С ним что делать?
А какой глубокий смысл во временных директориях? Никогда их не создавал, всегда просто файлы в TEMP, если нужно много файлов — то пусть будет много. Директории, по моему, нужны только установщикам, и то постаравшись можно без них обойтись.
S>Как это дело оформлять будем? Через static-событие, делегатом в конструкторе TempFile, простым дампом в Trace или ещё как?
S>Как вариант, можно завести доп. источник для всей библиотеки (по аналогии с PresentationTraceSources в WPF) и писать туда.
У меня, кстати, тоже была мысль про событие, так что поддерживаю.
Еще, по-моему, неплох вариант, который уже частично реализован в DisposableExtensions — передача хендлера, который реализует нужную обработку.
Перегрузить конструктор. Кому нужна обработка, использует его. Кому не нужна — обойдется прежним конструктором. Так можно основные варианты использования будет закрыть, поскольку в обсуждении высказывались мысли как за обработку, так и без нее.
Здравствуйте, aloch, Вы писали:
A>А какой глубокий смысл во временных директориях?
Ну вот пара реальных сценариев:
1. Создать папку, заполнить, скормить стороннему софту, грохнуть.
2. Создать временную папку при запуске приложения (необязательно с рандомным именем), грохнуть по завершению.
Здравствуйте, Vasiliy2, Вы писали:
V>У меня, кстати, тоже была мысль про событие, так что поддерживаю.
Ну вот да, пока склоняюсь к варианту от rm822: заводим TraceSource для всей библиотеки CodeJam и логируем проглоченное исключение туда, кого не устраивает — стройным шагом идут писать свой хелпер. Иначе логика по разруливанию исключений получается заметно сложнее самого хелпера.
и не задал нехороший вопрос: а что нужно делать, если временный файл удалить не получается? С контрольным аргументом: куча последовательных вызовов выкушает всё место и наступит полная печаль
Если имеет место куча последовательных вызовов при том, что прежний файл уже не нужен, то можно создать этот файл один раз и переиспользовать.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Если имеет место куча последовательных вызовов при том, что прежний файл уже не нужен, то можно создать этот файл один раз и переиспользовать.
Угу. Но тут необязательно последовательные вызовы нужны. Достаточно криво написанного антивируса или заиграться с ACL и запретить delete и рано или поздно получится некрасиво.
Здравствуйте, Sinix, Вы писали:
PD>>Если имеет место куча последовательных вызовов при том, что прежний файл уже не нужен, то можно создать этот файл один раз и переиспользовать. S>Угу. Но тут необязательно последовательные вызовы нужны. Достаточно криво написанного антивируса или заиграться с ACL и запретить delete и рано или поздно получится некрасиво.
Если начать играть с ACL со своим собственным временным файлом, то хорошего будет мало, но зачем это делать ?
Антивирус — возможно, но не очень уж вероятно, можно пренебречь.
Здравствуйте, Sinix, Вы писали:
S>Ну вот пара реальных сценариев: S>1. Создать папку, заполнить, скормить стороннему софту, грохнуть.
Создали в директорию с временными файлами, в директории для временных файлов, скормили софту.
Либо сами пишем код, ждем пока сторонний софт сделает все что ему надо, хз. сколько тут будем ждать(зависит от стороннего софта, и от нашей программы), может секунду,может минуту. Потом убираем все локи с файла и удаляем или просто забиваем(если файл не сильно большой или их не много или у нас не получилось снять все локи), пусть ОС сама чистит. S>2. Создать временную папку при запуске приложения (необязательно с рандомным именем), грохнуть по завершению.
Тут. При запуске смотреть если директория с таким именем уже существует — дропать ее.
Підтримати Україну у боротьбі з країною-терористом.
Здравствуйте, Sinix, Вы писали:
S>UPD. И да, FileOptions.DeleteOnClose подходит не всегда. Чтоб не спорить — у нас точно такой же хелпер есть для директорий. Что с ними делать будем? ; )
Нет абсолютно никакого способа закрыть доступ к некоему каталогу. Поэтому, и гарантировать его удаление нельзя.
Здравствуйте, Слава, Вы писали:
S>>UPD. И да, FileOptions.DeleteOnClose подходит не всегда. Чтоб не спорить — у нас точно такой же хелпер есть для директорий. Что с ними делать будем? ; )
С>Нет абсолютно никакого способа закрыть доступ к некоему каталогу. Поэтому, и гарантировать его удаление нельзя.