#php
Вопрос:
У меня периодически возникает проблема, когда PHP выдает предупреждение о том, что временный файл не существует, даже непосредственно после его размера ().
$external_file = fopen($url, 'rb');
if ($external_file) {
// store in a well-known location that won't get collected by system.
$tmpfile = tempnam(__DIR__.'/cache/', 'test');
$newfile = fopen($tmpfile, 'wb');
if ($newfile) {
while (!feof($external_file)) {
fwrite($newfile, fread($external_file, 1024 * 64), 1024 * 64);
}
fclose($newfile);
fclose($external_file);
} else {
throw new Exception('Could not open file for writing');
}
if (filesize($tmpfile)) {
// this sometimes throws a warning that $tmpfile doesn't exist
// even through we just got the filesize for it.
if (!rename($tmpfile, $filename)) {
error_log('manual move try');
// this is failing too.
error_log(`mv {$tmpfile} {$filename}`);
}
}
}
Ничто на самом деле не работает последовательно. Частота отказов составляет ~5%. Поскольку он прерывистый, мы ожидаем, что это какое-то состояние гонки-я даже попытался поместить переименование в цикл while с 10 мс сна при 1000 итерациях. Только что в моем журнале ошибок было 1000 предупреждений.
Как вы можете видеть выше, даже пытался выйти из PHP и запустить его, но безуспешно.
Есть более сложная обработка (перенесена сюда, чтобы упростить основной случай), которая тоже не сработала, например, попытка принудительно закрыть файл… но файл довольно явно закрыт. Этот цикл while выполняется не более одного раза, насколько я могу судить, без повторной реализации и трассировки.
// have read that file handles are not released
// immediately sometimes.
$iterations = 0;
while (is_resource($newfile)) {
if($iterations){
usleep(10000);
}
if ($iterations > 1000) {
error_log('MAXIMUM LOOP ITERATIONS.');
break;
}
//Handle still open
fclose($newfile);
$iterations ;
// 10ms isn't going to kill anyone.
}
Пробовал и несколько других экзотических вещей, но безуспешно. Работает на Ubuntu 20.04.
Комментарии:
1. Дикое предположение: вы пробовали clearstatcache() перед вызовом функции filesize() ?
2. ПРИВЕТ @JohnGreen, каково значение $filename ? (случайный или фиксированный ?)
Ответ №1:
Если этот сценарий выполняется одновременно, созданный запрос A $tmpfile
, скорее всего, будет переименован по запросу B после filesize
выполнения в запросе A. Существует два решения:
- используйте
flock
для блокировки$tmpfile
- используйте случайное имя файла для
$tmpfile
Комментарии:
1.Его PHP. Не уверен, что понимаю, что вы имеете в виду параллелизм. Это, как говорится,
fclose
освободитflock
(что, по-видимому, не является проблемой) и$tmpfile
является случайным именем файла (черезtempnam
). Кроме того-fwiw, я, конечно, пытался запустить операцию переименования вне операции изменения размера файла.2. Я думаю, что они имеют в виду два экземпляра сценария, запущенных одновременно, которые потенциально могут генерировать одно и то же «случайное» имя файла. Если это действительно так, то вам придется
flock($newfile, LOCK_EX)
сделать это после открытия, чтобы убедиться, что другой процесс PHP не сможет получить к нему доступ, пока он не будет закрыт. Но у вас все равно будет проблема: как только вы закроете его, теперь он доступен для записи другим процессом непосредственно перед перемещением. В любом случае, почему бы вам просто не написать напрямую$filename
, а не использовать промежуточный временный файл?3. извините, я только что протестировал
tempnam
функцию, она никогда не возвращает повторяющиеся имена ~~4. @kmoser Ну, это было случайное имя. Но я удвоил случайность, сгенерировав уникид с высокой энтропией. Это не решило проблему. Итак, согласно вашему предложению, я попытался написать непосредственно в файл (раньше я этого не делал, потому что в случае сбоя было легче очистить). Это не сработало, поэтому я попробовал стаю на прямой записи файла. К тому же, ничего хорошего.
5. вы можете протестировать тот же код на другом сервере, возможно, проблема связана с устройством хранения