Оригинал взят у
david_m в
Про утечку ресурсов в генераторах PHPОчень интересный и полезный момент сегодня всплыл в комментариях на Хабре. В PHP 5.5, как известно, сделали поддержку функций-генераторов, по типу питоновских. Там раньше были итераторы, но с адовым синтаксисом (как всё в SPL), а теперь ввели оператор 'yeld' и всё волшебным образом упростилось.
Например, можно написать такой генератор, читающий построчно файл:
function getLines($file) {
f = fopen($file, 'r');
while ($line = fgets($f)) {
yield $line;
}
fclose($f);
}
'yield' означает «вернуть значение и продолжить с этого места при следующем вызове функции». Имея такой генератор, можно сделать вот такую печать файла:
foreach (getLines("file.txt") as $line) {
echo $line;
}
Удобно? Очень удобно. Оператор 'yeld' выдаст все строки файла, а потом, когда файл закончится, произойдёт обычный 'return' из функции, который закроет генератор (и закончит цикл).
Но как известно, если всё идёт хорошо,
значит, вы чего-то не заметили. Немного изменим наш цикл:
foreach (getLines("file.txt") as $n => $line) {
if ($n > 5) break;
echo $line;
}
Предположим, нас интересуют только первые шесть строк файла, а дальше мы хотим прервать цикл оператором 'break'. Имеем на то полное право. Но что в этом случае произойдёт внутри генератора? А ничего. Он останется стоять на последнем исполненном yeld-е и никогда не дойдёт до строки 'fclose($f)'. И наш файл останется незакрытым.
Мы получили утечку ресурса (открытого файла). Понятно, что внутри генератора могут быть открыты любые ресурсы и объекты, и их необходимо правильно и предсказуемо закрывать. Но как это сделать, если юзер может в любой момент сделать break? Обычная документация (
http://www.php.net/manual/en/language.generators.overview.php) никаких намёков не даёт.
Так вот, оказывается (и за это спасибо юзеру weirdan с Хабра:
http://habrahabr.ru/post/189796/#comment_6594776), что читать в этом случае надо не документацию, а RFC по генераторам:
https://wiki.php.net/rfc/generators#closing_a_generator. А в нём сказано, что при освобождении ссылки на генератор, у него внутри обязаны выполниться все блоки 'finally'. И тогда мы получаем очень простой, красивый и безопасный код:
function getLines($file) {
f = fopen($file, 'r');
try {
while ($line = fgets($f)) {
yield $line;
}
} finally {
fclose($f);
}
}
В этом случае блок 'finally' выполнится и при нормальном выходе из цикла по генератору и при выходе по break-у. Ура.
Так что если вы пишете на PHP - имейте это в виду. Потому что, кажется, больше нигде про это узнать невозможно - ни в одной из читанных мною статей по генераторам не было ни слова об утечке ресурсов.