avva: (Default)
[personal profile] avva
А вот вчера обнаружился любопытный баг:

Есть программа synsuck.pl, которая скачивает с сети RSS-ленты, из которых строятся syndicated accounts. Она раньше была устроена примерно так:

- строим запрос к базе данных, вытягивающий данные об аккаунтах, к-е нужно обновить
- while(вытягиваем аккаунт из базы данных) {

делаем кучу всего для его обновления: скачиваем RSS-файл, парсим его с помощью XML-парсера, проверяем, появились ли там новые записи, вносим их в нашу базу данных

}

Однако со временем этих аккаунтов стало так много, что программа элементарно перестала за ними поспевать. Поэтому несколько дней назад её сделали (не я) многопотоковой, после чего она стала выглядеть примерно так. Сначала идёт внутренняя функция:

my $process_user = sub {

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

}

Потом идёт цикл, в котором главный процесс порождает детей с помощью fork, и каждый ребёнок вызывает функцию $process_user для своего аккаунта. Цикл выглядит примерно так (напомню для незнающих Perl, что "next;" в нём — то же, что "continue;" в C, переход к началу цикла):

while(есть, что делать) {
  
 if(my $pid = fork) {
    # я - отец
    записываем $pid сына в список работ
    next; # переход к началу цикла
 } else {
    # я - сын
    my $account = get_next_user();
    $process_user->($account);
    exit(0); # этот процесс заканчивает работу
 }
}


В общем, всё это не работало. Дерево процессов, вместо одного отца и кучи созданных им детей, выглядело так, будто сыновья создавали, в свою очередь, других сыновей, что кажется совершенно невозможным согласно логике кода, ведь сразу после возвращения из процедуры обработки аккаунта каждый сын умирает вследствие вызова exit().

Мне пришлось с этим разбираться. Я довольно долго не понимал, что же, чёрт побери, происходит. Сделал специальную версию программы, в которой убрал всю RSS-логику, оставил только логику обработки детей (т.е. $process_user ничего не делает, только спит несколько секунд для отвода глаз) и кучу debug prints. Но в этой версии у меня не получалось повторить неправильное поведение. Я ещё немного потыкался и наконец разобрался.

Оказывается, в функции $process_user, ещё с тех времён, когда она была внутренностью большого цикла, осталась куча операторов "next;" в разных случаях, когда происходят какие-нибудь ошибки скачивания, или парсинга, или нет новых записей для базы данных. Но теперь этот код — не внутренность цикла, а часть функции, в которой никаких циклов нет; что же делают эти "next;"?

Они выводят управление за пределы функции и действуют на обволакивающий её вызов цикл в теле программы.

Т.е. в схеме, обрисованной выше, "next;" внутри вызова функции $process_user->($account) возвращает управление из функции и прыгает на начало главного цикла while(), избегая таким образом вызова exit(0); более того, сын начинает вследствие этого вести себя так, будто он — отец, и сам порождает нового сына, и так далее.

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

Date: 2003-10-08 12:06 pm (UTC)
From: [identity profile] ex-ilyavinar899.livejournal.com
Правильно ли я тебя понял, что в Пёрле для next - dynamic scoping вместо lexical scoping?

Date: 2003-10-08 12:17 pm (UTC)
From: [identity profile] avva.livejournal.com
Судя по всему, да.

Date: 2003-10-08 12:36 pm (UTC)
From: [identity profile] ex-ilyavinar899.livejournal.com
Хороший пример того, почему виртуальная машина, заточенная под Си-шарп, будет плохо приспособленной даже к такому не очень необычному языку, как Пёрл.

Date: 2003-10-08 01:12 pm (UTC)
From: [identity profile] dimrub.livejournal.com
Мне кажется, описанноое Аввой поведение не является примером, подтверждающим это утверждение. Компилятор перла над IL должен всего лишь учитывать этот факт, и вести себя соответственно. Возможно, mapping будет не очень очевидный, но что с того.

Date: 2003-10-08 01:16 pm (UTC)
From: [identity profile] ex-ilyavinar899.livejournal.com
т.е. для каждого цикла он должен записать Куда Следует, что вот, мы вошли в цикл, так, чтобы если субрутина, вызываемая из цикла, или субрутина, вызываемая из субрутины, вызываемой из цикла, захотела из этого цикла выйти, этот выход можно было бы произвести.

По-моему, это очень плохо накладывается на понятия .NET CLR.

Date: 2003-10-08 01:19 pm (UTC)
From: [identity profile] dimrub.livejournal.com
Скорее наоборот, для каждого next он должен указать, к чему этот next относится (т.е. поставить implicit label).

Date: 2003-10-08 01:26 pm (UTC)
From: [identity profile] ex-ilyavinar899.livejournal.com
А если это статически нельзя определить, как в Аввыном примере?

Date: 2003-10-08 01:52 pm (UTC)
From: [identity profile] dimrub.livejournal.com
Тэкс, что мы имеем? Раскручивание стэка до ближайшего контекста, находящегося в цикле? Я думаю, исключения идеально подходять для этой ситуации.

Date: 2003-10-08 02:49 pm (UTC)
From: [identity profile] ex-ilyavinar899.livejournal.com
то есть, вокруг каждого вызова субрутины в цикле -

try
{
...
}
catch (next_exception ne)
{
goto next_label;
}
catch (continue_excetion ce)
{
goto continue_label;
}
catch (last_exception be)
{
goto last_label;
}

Date: 2003-10-08 02:56 pm (UTC)
From: [identity profile] dimrub.livejournal.com
Нет. Каждый цикл - внутри try block. А catch делает continue. Возможно, это не оптимальное решение, можно оптимизировать - вставлять такие блоки только вокруг тех циклов, в которых вызываются функции, из которых такое исключение может быть брошено (к сожалению, в отличие от джавы, в C# и CLR нет ключевого слова throws).

Date: 2003-10-10 08:35 am (UTC)
From: [identity profile] gdy.livejournal.com
Chris Brumme про исключения в CLR.
However, there is a serious long term performance problem with exceptions and this must be factored into your decision.Consider some of the things that happen when you throw an exception. (http://blogs.gotdotnet.com/cbrumme/default.aspx?date=2003-10-01T00:00:00)

Date: 2003-10-08 06:25 pm (UTC)
From: [identity profile] ex-ilyavinar899.livejournal.com
http://www.parrotcode.org/faq/#why_your_own_virtual_machine_why_not_compile_to_jvm/.net

Интересно, что это за various reasons.

Date: 2003-10-08 12:18 pm (UTC)
From: [identity profile] xfyre.livejournal.com
все правильно: next в перле - это не оператор, а функция.
и действует она соответственно.

perldoc perlfunc, там много любопытного написано.
абсолютно та же фигня с last и continue :)

Date: 2003-10-08 12:23 pm (UTC)
From: [identity profile] avva.livejournal.com
Ну вот в perlfunc написано:

"next" cannot be used to exit a block which returns a value
such as "eval {}", "sub {}" or "do {}", and should not be used
to exit a grep() or map() operation.

чего я и ожидал, собственно. Но на практике выходит по-другому.

Date: 2003-10-08 12:34 pm (UTC)
From: [identity profile] xfyre.livejournal.com
так она же не exit! она честно next внешний цикл! :)
только что специально проверил это на следующем простом примере:

#!/usr/bin/perl

use strict;
use vars qw($global_var $i);

$global_var = "before testSub()";
$i = 1;

while ( $i-- ) {
    &testSub();
}

print $global_var, "\n";

sub testSub {
    next;
    $global_var = "after testSub()";
}


угадайте с одного раза, что он выводит?

на самом деле, в перле полно подобных фишек. одна только имплементация try { ... } catch ... with { }; чего стоит.

Date: 2003-10-08 12:38 pm (UTC)
From: [identity profile] avva.livejournal.com
Гм. У меня он выводит

$ perl 3.pl
before testSub()


т.е. вызов next; внутри testSub приводит к выходу из testSub, и
присвоение $global_var = "after testSub()"; не выполняется ;)

Date: 2003-10-08 12:43 pm (UTC)
From: [identity profile] xfyre.livejournal.com
именно :)

а вот что про это сказано в perlsyn (найдено по той дискуссии, на которую давали ссылку ниже):


The while statement executes the block as long as the expression is true (does not evaluate to the null string "" or 0 or "0"). The LABEL is optional, and if present, consists of an identifier followed by a colon. The LABEL identifies the loop for the loop control statements next, last, and redo. If the LABEL is omitted, the loop control statement refers to the innermost enclosing loop. This may include dynamically looking back your call-stack at run time to find the LABEL. Such desperate behavior triggers a warning if you use the use warnings pragma or the -w flag. Unlike a foreach statement, a while statement never implicitly localises any variables.


т.е. это, ко всему прочему, documented behaviour, как и большинство других странностей перла :)

Date: 2003-10-08 12:47 pm (UTC)
From: [identity profile] avva.livejournal.com
Да, я тоже это прочитал. Но согласитесь, что это противоречит процитированной выше документации next в perlfunc.
А т.к. именно документация next специально упоминает случай sub {}, то ей как бы больше веры должно быть, но она оказывается неверной ;)

Date: 2003-10-08 12:53 pm (UTC)
From: [identity profile] xfyre.livejournal.com
с точки зрения здравого смысла - да, документация противоречива.

с формальной точки зрения - все правильно. попробуйте в вышеприведенном примере закомментировать цикл и оставить только вызов &testSub() - будет compile error ;)

Date: 2003-10-09 01:24 am (UTC)
nine_k: A stream of colors expanding from brain (Default)
From: [personal profile] nine_k
>с точки зрения здравого смысла - да, документация противоречива.

True Perl spirit ;-]

Date: 2003-10-08 12:21 pm (UTC)
From: [identity profile] saltovski.livejournal.com
А баг в стандарте или в самом интерпретаторе?

Date: 2003-10-08 12:24 pm (UTC)
From: [identity profile] avva.livejournal.com
Баг в программе ;) т.е. эти next; должны были быть return; в любом случае.

Почему next; именно так себя ведёт, соответствует ли это официальному описанию языка, я ещё не разобрался.

Date: 2003-10-08 12:35 pm (UTC)
From: [identity profile] darxeth.livejournal.com
Весьма странно и интересно. Я нашёл обсуждение (ссылка (http://groups.google.com/groups?hl=ru&lr=&ie=UTF-8&oe=UTF-8&threadm=WOa9OEO34SkcqBGgGZ7QInSS56hm%404ax.com&rnum=1&prev=/groups%3Fq%3Dnext%2Bsub%2Bperlfunc%26hl%3Dru%26lr%3D%26ie%3DUTF-8%26oe%3DUTF-8%26selm%3DWOa9OEO34SkcqBGgGZ7QInSS56hm%25404ax.com%26rnum%3D1)) этого вопроса в Google groups.
Не знаю насколько полезно, сам сейчас читаю.

Date: 2003-10-08 12:41 pm (UTC)
From: (Anonymous)
Документация нам говорит: "next cannot be used to exit a block which returns a value such as eval {}, sub {} or do {}, and should not be used to exit a grep() or map() operation."

однако,


sub x{
next;
}
$i=1;
while($i<20){
$i++;
x();
print $i;
}


Что, конечно, печально

Date: 2003-10-08 12:42 pm (UTC)
From: (Anonymous)
в смысле то, что оно ничего не напечатает

Date: 2003-10-08 12:45 pm (UTC)
From: [identity profile] avva.livejournal.com
Вот-вот.

Правда, в perldoc perlsyn пишут, что

The LABEL identifies the loop for the loop control statements "next", "last", and "redo". If the LABEL is omitted, the loop control statement refers to the innermost enclosing loop. This may include dynamically looking back your call-stack at run time to find the LABEL. Such desperate behavior triggers a warning if you use the "use warnings" pragma or the -w flag.

И действительно, если запустить perl -w, то он говорит

Exiting subroutine via next at testsub.pl line 15.

Налицо противоречие в документации.

Date: 2003-10-08 12:52 pm (UTC)
From: [identity profile] cema.livejournal.com
Потому что в Перле не функции, а подпрограммы. :-)

В сторону: что же теперь, все циклы метками помечать?

Date: 2003-10-08 11:27 pm (UTC)
From: [identity profile] sobaker.livejournal.com
Т.е. continue выпрыгивает из функции?

Боже, какой дикий бред :)

Date: 2003-10-10 08:55 am (UTC)
From: [identity profile] gdy.livejournal.com
Principle of Least Surprise (http://www.artima.com/intv/ruby4.html)
For example, I was a C++ programmer before I started designing Ruby. I programmed in C++ exclusively for two or three years. And after two years of C++ programming, it still surprised me.

Date: 2003-10-10 09:08 am (UTC)
From: [identity profile] sobaker.livejournal.com
Кстати, да. С++ - громоздкий и сложный язык.

Date: 2003-10-10 06:18 pm (UTC)
From: [identity profile] kot-begemot.livejournal.com
Сто двадцать пятое подтверждение того, что perl не следует использовать помимо элементарного парсинга, в каковой функции он не сильно превосходит awk. Вывод - perl - это никому не нужное извращение дилетанта, пользоваться которым просто опасно.

December 2025

S M T W T F S
  123 4 56
78 9 10 11 1213
1415 1617181920
21 22 23 24 2526 27
2829 30 31   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 1st, 2026 06:34 pm
Powered by Dreamwidth Studios