Пишу, по большей части, про историю, свою жизнь и немного про программирование.

Неблокирующий режим файлов в PHP

Смотрел недавно исходники языка ПХП, хотел изучить подробнее как реализованы там стримы. Пока читал, случайно натолкнулся на недокументированную возможность — у функции fopen, как оказалось, есть режим открытия в неблокирующем режиме (буква n).

#if defined(O_NONBLOCK)
        if (strchr(mode, 'n')) {
                flags |= O_NONBLOCK;
        }
#endif

Доступно не на всех системах, только там, где есть константа O_NONBLOCK в fcntl.h, то есть в Линуксах, например.

Для меня это довольно полезное знание, так как я давно ищу возможность проверить из ПХП не зависла ли подмонтированная через NFS система хранения. Из-за таких зависаний виснет драйвер в ядре и все системные вызовы, которые к нему обращаются. В конечном счёте виснет процесс, который пытается работать с такой файловой системой, да так, что его не получается убить через SIGKILL.

В общем, надо будет попробовать при следующем таком инциденте — не спасает ли открытие в неблокирующем режиме. Код, кажется, должен выглядеть примерно так:

function tryToOpen(string $path, float $timeout): ?bool
{
	$dio_fd = dio_open($path, O_RDONLY | O_NONBLOCK);
	if ($dio_fd === false) {
		return null;
	}

	foreach (glob('/proc/self/fd/*') as $file) {
	    if (is_link($file) && readlink($file) === $path) {
		$fd = basename($file);
		$read = [fopen("php://fd/$fd", 'rn')];
		$write = $except = null;

		$timeout_us = (int) ($timeout * 1e6);
		return stream_select($read, $write, $except, 0, $timeout_us) === 1;
             }
	}
	return null;
}

Тут используется модуль ПХП Direct IO, чтобы обратиться к системной функции open мимо ПХП. Приходится так делать, потому что функция fopen, которая есть в языке, сначала попытается проверить существование файла и в моей задаче сразу зависнет.

После этого я, пользуясь /proc, ищу среди своих файловых дескрипторов тот, который отвечает за только что открытый файл и переоткрываю его через fopen (в этом месте делается дубликат файлового дескриптора, а прежний закрывается — вызывается dup), так как мы тут работаем с номером файлового дескриптора, то проверка на существование файла не используется.

Переоткрытие файла нужно, так как в вызов stream_select, который я использую для ожидания данных из файла с таймаутом, можно передать результат fopen, но не dio_open.

Надеюсь случай испытать этот код представится нескоро.

1 комментарий
Леонид 2022

А почему tryToOpen а не tryOpen ?

Евгений Степанищев 2022

Ну я даже не знаю как ответить на этот вопрос. А почему должно быть tryOpen?