PHP file_exists иногда возвращает false для файла на общем ресурсе CIFS

У меня есть общий ресурс CIFS из Windows Server 2012 R2, установленный на Ubuntu 14.04.2 LTS (ядро 3.13.0-61-generic), как это

/etc/fstab

//10.1.2.3/Share /Share cifs credentials=/root/.smbcredentials/share_user,user=share_user,dirmode=0770,filemode=0660,uid=4000,gid=5000,forceuid,forcegid,noserverino,cache=none 0 0

gid=5000 соответствует группе www-data, которая запускает процесс PHP.

Файлы монтируются правильно, когда я проверяю через консоль, войдя в систему как пользователь www-data - они доступны для чтения и удаления (операции, которые используются PHP-скриптом).

PHP-скрипт обрабатывает около 50-70 000 файлов в день. Файлы создаются на хост-машине Windows, а через некоторое время PHP-скрипт, работающий на машине Linux, уведомляется о новом файле, проверяет, существует ли файл (file_exists), читает его и удаляет. Обычно все работает нормально, но иногда (от нескольких сотен до 1-2 000 в день) PHP-скрипт выдает ошибку, что файл не существует. Этого никогда не должно быть, поскольку он уведомляется только о реально существующих файлах.

Когда я вручную проверяю эти файлы, указанные как несуществующие, они корректно доступны на компьютере с Ubuntu и имеют дату создания до того, как PHP-скрипт проверил их существование.

Затем я запускаю PHP-скрипт вручную, чтобы подобрать этот файл, и он подхватывается без проблем.

Что я уже пробовал

Есть несколько похожих вопросов, но я, кажется, исчерпал все советы:

  • Я добавил clearstatcache() перед проверкой file_exists($f)
  • Права доступа к файлам и каталогам в порядке (в дальнейшем точно такой же файл подхватывается корректно)
  • Путь, используемый для проверки file_exists($f), является абсолютным путем без специальных символов — пути к файлам всегда имеют формат /Share/11/222/333.zip (с различными цифрами)
  • Я использовал параметр монтирования общего ресурса noserverino
  • Я использовал параметр монтирования общего ресурса cache=none

/proc/fs/cifs/Stats/ отображается, как показано ниже, но я не знаю, есть ли здесь что-то подозрительное. Речь идет о доле 2) \\10.1.2.3\Share.

Resources in use
CIFS Session: 1
Share (unique mount targets): 2
SMB Request/Response Buffer: 1 Pool size: 5
SMB Small Req/Resp Buffer: 1 Pool size: 30
Operations (MIDs): 0

6 session 2 share reconnects
Total vfs operations: 133925492 maximum at one time: 11

1) \\10.1.2.3\Share_Archive
SMBs: 53824700 Oplocks breaks: 12
Reads:  699 Bytes: 42507881
Writes: 49175075 Bytes: 801182924574
Flushes: 0
Locks: 12 HardLinks: 0 Symlinks: 0
Opens: 539845 Closes: 539844 Deletes: 156848
Posix Opens: 0 Posix Mkdirs: 0
Mkdirs: 133 Rmdirs: 0
Renames: 0 T2 Renames 0
FindFirst: 21 FNext 28 FClose 0
2) \\10.1.2.3\Share
SMBs: 50466376 Oplocks breaks: 1082284
Reads:  39430299 Bytes: 2255596161939
Writes: 2602 Bytes: 42507782
Flushes: 0
Locks: 1082284 HardLinks: 0 Symlinks: 0
Opens: 2705841 Closes: 2705841 Deletes: 539832
Posix Opens: 0 Posix Mkdirs: 0
Mkdirs: 0 Rmdirs: 0
Renames: 0 T2 Renames 0
FindFirst: 227401 FNext 1422 FClose 0

Один шаблон, который я вижу, заключается в том, что ошибка возникает только в том случае, если рассматриваемый файл уже был обработан (прочитан и удален) ранее PHP-скриптом. Есть много файлов, которые были правильно обработаны, а затем снова обработаны позже, но я никогда не видел такой ошибки для файла, который обрабатывается в первый раз. Время между повторными обработками варьируется от 1 до примерно 20 дней. Для повторной обработки файл просто воссоздается по тому же пути на хосте Windows с обновленным содержимым.

В чем может быть проблема? Как лучше провести расследование? Как я могу определить, лежит ли проблема на стороне PHP или ОС?


Обновить

Я переместил программное обеспечение, которое создает файлы, на виртуальную машину Ubuntu, которая монтирует те же общие ресурсы таким же образом. Этот компонент написан на Java. Я не вижу никаких проблем при чтении/записи файлов.


Обновление – сведения о PHP

Точный код PHP:

$strFile = zipPath($intApplicationNumber);

clearstatcache();

if(!file_exists($strFile)){
    return responseInternalError('ZIP file does not exist', $strFile);
}

intApplicationNumber — это параметр запроса (например, 12345678), который просто преобразуется в путь функцией zipPath() (например, \Share\12\345\678.zip — всегда полный путь).

Сценарий может быть запущен одновременно с разными номерами приложения, но не будет запущен одновременно с одним и тем же номером приложения.

Если скрипт не работает (возвращает ошибку 'ZIP file does not exist'), он будет вызван снова через минуту. Если это не удастся, он будет навсегда помечен как неудавшийся. Затем, обычно более чем через час, я могу вызвать скрипт вручную с тем же вызовом (GET-запросом), что и на производстве, и он отлично работает, файл найден и отправлен в ответ:

public static function ResponseRaw($strFile){
    ob_end_clean();
    self::ReadFileChunked($strFile, false);
    exit;
}

protected static function ReadFileChunked($strFile, $blnReturnBytes=true) {
    $intChunkSize = 1048576; // 1M
    $strBuffer = '';
    $intCount = 0;
    $fh = fopen($strFile, 'rb');

    if($fh === false){
        return false;
    }

    while(!feof($fh)){
        $strBuffer = fread($fh, $intChunkSize);
        echo $strBuffer;
        if($blnReturnBytes){
            $intCount += strlen($strBuffer);
        }
    }

    $blnStatus = fclose($fh);

    if($blnReturnBytes && $blnStatus){
        return $intCount;
    }

    return $blnStatus;
}

После того, как клиент получает файл, он уведомляет PHP-сервер, что файл можно переместить в архив (с помощью copy() и unlink()). Эта часть работает нормально.


Результат STRACE

После нескольких дней без ошибок ошибка снова появилась. Я запустил strace, и он сообщает

access("/Share/11/222/333.zip", F_OK) = -1 ENOENT (No such file or directory)

для некоторых файлов, которые существуют, когда я запускаю ls /Share/11/222/333.zip из командной строки. Поэтому проблема на уровне ОС, PHP не виноват.

Ошибки начали появляться, когда нагрузка на диск на хосте увеличилась (из-за других процессов), поэтому предложение @risyasin ниже кажется наиболее вероятным - это вопрос занятых ресурсов/тайм-аутов.

Я попробую совет @ miguel-svq пропустить тест на существование и сразу перейти к fopen(), а затем обработать ошибку. Я посмотрю, изменит ли это что-нибудь.


person Adam Michalik    schedule 18.02.2016    source источник
comment
Хороший вопрос. Я не первый раз слышу о ненадежности чего-то подобного. Обходной путь, который немного поможет вам, может состоять в том, чтобы повторить попытку file_exists и не останавливать скрипт сразу.   -  person Daniel W.    schedule 18.02.2016
comment
Спасибо @DanFromGermany - да, это одна из грязных идей, которые у меня были - повторить попытку (даже после паузы в N секунд), если это какая-то временная заминка. Но очень хотелось бы понять почему так происходит и исправить в руте.   -  person Adam Michalik    schedule 18.02.2016
comment
Я действительно не думаю, что это касается php, но nfs. могут быть тайм-ауты или занятые ресурсы, поскольку они зависят от сети. strace и tcpdump с обеих сторон, чтобы увидеть, что на самом деле происходит, могут дать вам подсказки. также попробуйте с пользователем php/webserver при тестировании.   -  person risyasin    schedule 18.03.2016
comment
Абсолютно согласен с @risyasin, вероятно, nfs, а не php. Пожалуйста, если вы решите это или обнаружите, почему это происходит, дайте нам знать. У меня была аналогичная проблема несколько лет назад, и я обработал ее, пропустив проверку file_exists и напрямую попытавшись открыть и прочитать файл...   -  person miguel-svq    schedule 22.03.2016
comment
Я поддерживаю других, это, скорее всего, проблема с NFS или SMB2. Возможно, это связано с кэшированием на сервере Windows. Посмотрите DirectoryCacheLifetime и комментарии по адресу technet.microsoft. com/en-us/library/ff686200(WS.10).aspx   -  person John P    schedule 22.03.2016
comment
@John P - да, я уже читал эту статью, но насколько я понимаю, эти настройки применяются к кэшированию, если Windows является клиентом, верно? В любом случае, установка всех значений на ноль не принесла улучшения. На этой неделе меня не будет в офисе, на следующей неделе я вернусь с дополнительной информацией (журналы трассировки). Спасибо всем за подсказки.   -  person Adam Michalik    schedule 22.03.2016
comment
Так как же PHP-скрипт на самом деле уведомляется об ожидании файла и как это происходит? Оставит ли он файл на месте, пока он не завершит обработку? Могут ли несколько экземпляров сценария PHP получать уведомления для одного и того же файла, если обработка занимает слишком много времени?   -  person Gralgrathor    schedule 27.03.2016
comment
@Axalix - спасибо, я попробую добавить file_exists(realpath($f)), который является предложенным обходным путем в первой предоставленной вами ссылке. Второй пост посвящен 64-битным номерам инодов в 32-битных системах — я уже сталкивался с этим постом и смонтировал общий ресурс с опцией noserverino. Кроме того, Ubuntu 64-битная, так что это не должно быть проблемой...   -  person Adam Michalik    schedule 29.03.2016
comment
@Gralgrator - я добавил детали PHP-кода.   -  person Adam Michalik    schedule 29.03.2016
comment
Вы могли бы попробовать fopening, чтобы увидеть, существует ли он, может быть, у него больше шансов на успех?   -  person toster-cx    schedule 31.03.2016
comment
Я в порядке с danofgermany. Помимо работы с сетевым ресурсом, обработка ошибок должна знать, что сеть может выйти из строя. Таким образом, у вас должен быть какой-то счетчик ошибок для каждого файла и пометить их для повторного запуска позже в резервном цикле (это лучше, чем жить в цикле при ошибке, потому что это более информативно)   -  person quazardous    schedule 09.04.2016
comment
Как насчет того, чтобы запланировать это на определенное время и дату, когда ресурсы будут доступны? Просто предложение, но я думаю, что у quazardous есть правильная идея.   -  person yardpenalty.com    schedule 06.05.2016
comment
Сработало ли удалить проверку существования и сразу использовать fopen ??? У меня такая же проблема.   -  person ADJenks    schedule 25.03.2021


Ответы (2)


Вы можете попробовать использовать параметр directio, чтобы избежать кэширования данных inode для файлов, открытых на этом монтировании:

//10.1.2.3/Share /Share cifs credentials=/root/.smbcredentials/share_user,user=share_user,dirmode=0770,filemode=0660,uid=4000,gid=5000,forceuid,forcegid,noserverino,cache=none,directio 0 0
person javierfdezg    schedule 31.03.2016
comment
справочная страница говорит, что эта опция будет объявлена ​​устаревшей в версии 3.7. Вместо этого пользователям следует использовать cache=none на более поздних ядрах. У меня ядро ​​3.13 и у меня уже есть cache=none. Имеет ли тогда смысл использовать directio? - person Adam Michalik; 01.04.2016

Вряд ли это окончательный ответ на мою проблему, скорее краткое изложение того, что я узнал и с чем пришел к выводу.

В основе проблемы лежит то, что именно ОС сообщает, что файл не существует. Запуск strace показывает время от времени

access("/Share/11/222/333.zip", F_OK) = -1 ENOENT (No such file or directory)

для файлов, которые существуют (и отображаются в списке с ls).

Хост общего доступа Windows иногда был под большой нагрузкой на диск. Что я сделал, так это переместил одну из общих папок на другой хост, чтобы теперь нагрузка распределялась между ними. Кроме того, в последнее время общая нагрузка на систему немного снизилась. Всякий раз, когда я получаю сообщение об ошибке о том, что файл не существует, я повторяю запрос через некоторое время, и его больше нет.

person Adam Michalik    schedule 03.06.2016