Ваш код предполагает, что операция file_put_contents()
- это atomi c, и что использования FLOCK_EX
и FLOCK_SH
достаточно, чтобы гарантировать отсутствие условий гонки между двумя программами. Это не тот случай .
Как видно из PHP do c, FLOCK_EX
применяется после открытия файла. Это важно, потому что у программы на C ++ остается короткое время, чтобы успешно открыть файл и заблокировать его с помощью FLOCK_SH
. В этот момент файл уже был усечен командой fopen()
, выполненной PHP, и он пуст.
Скорее всего, происходит следующее:
- PHP код открывает файл для записи, усечения и эффективного уничтожения его содержимого.
- Код C ++ открывает файл для чтения.
- Код C ++ запрашивает общую блокировку файла: блокировка предоставляется.
- PHP код запрашивает монопольную блокировку файла: вызовы блокируются, ожидая, пока блокировка станет доступной.
- Код C ++ читает содержимое файла: ничего, файл пуст.
- Код C ++ удаляет файл.
- Код C ++ освобождает разделяемую блокировку.
- PHP код получает исключительную блокировку.
- PHP код записывает в файл: данные не достигают диска, потому что индексный дескриптор, связанный с дескриптором открытого файла, больше не существует.
- Фактически у вас нет файла, и данные теряются.
Проблема с вашим кодом в том, что t Операции, которые вы выполняете с файлом из двух разных программ, не являются atomi c, и способ, которым вы устанавливаете блокировки, не помогает гарантировать, что они не перекрываются.
Единственный разумный способ гарантировать атомарность такой операции в POSIX-совместимой системе, даже не беспокоясь о блокировке файлов, заключается в использовании преимущества атомарности rename(2)
:
If newpath
уже существует, он будет заменен атомарно, чтобы не было точки, в которой другой процесс, пытающийся получить доступ к newpath
, не обнаружит, что он отсутствует.
Если newpath
существует, но операция по какой-то причине завершается неудачно, rename()
гарантирует, что экземпляр newpath
останется на месте.
Эквивалентная функция rename()
PHP - это то, что вы должны использовать в этом случае. Это самый простой способ гарантировать обновления файла atomi c.
Я бы предложил следующее:
PHP код:
$tmpfname = tempnam("/tmp", "myprefix"); // Create a temporary file.
file_put_contents($tmpfname, "contents"); // Write to the temporary file.
rename($tmpfname, "sampleDir/invoice.xml"); // Atomically replace the contents of invoice.xml by renaming the file.
// TODO: check for errors in all the above calls, most importantly tempnam().
Код C ++:
FILE* pInvoiceFile = fopen("sampleDir/invoice.xml", "r");
if (pInvoiceFile != NULL)
{
struct stat fileStat;
fstat(fileno(pInvoiceFile), &fileStat);
string invoice;
invoice.resize(fileStat.st_size);
size_t n = fread(&invoice[0], 1, fileStat.st_size, pInvoiceFile);
fclose(pInvoiceFile);
if (n == 0)
remove("sampleDir/invoice.xml");
}
Таким образом, программа на C ++ всегда будет видеть старую версию файла (если fopen()
происходит до PHP rename()
) или новой версии файла (если fopen()
происходит после), но он никогда не увидит несовместимую версию файла.