?

Log in

No account? Create an account
интересный баг - Поклонник деепричастий [entries|archive|friends|userinfo]
Anatoly Vorobey

[ website | Website ]
[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

Links
[Links:| English-language weblog ]

интересный баг [окт. 8, 2003|08:53 pm]
Anatoly Vorobey
А вот вчера обнаружился любопытный баг:

Есть программа 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); более того, сын начинает вследствие этого вести себя так, будто он — отец, и сам порождает нового сына, и так далее.

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

Comments:
From: ex_ilyavinar899
2003-10-08 12:06 pm
Правильно ли я тебя понял, что в Пёрле для next - dynamic scoping вместо lexical scoping?
(Ответить) (Thread)
[User Picture]From: avva
2003-10-08 12:17 pm
Судя по всему, да.
(Ответить) (Parent) (Thread)
From: ex_ilyavinar899
2003-10-08 12:36 pm
Хороший пример того, почему виртуальная машина, заточенная под Си-шарп, будет плохо приспособленной даже к такому не очень необычному языку, как Пёрл.
(Ответить) (Parent) (Thread) (Развернуть)
[User Picture]From: xfyre
2003-10-08 12:18 pm
все правильно: next в перле - это не оператор, а функция.
и действует она соответственно.

perldoc perlfunc, там много любопытного написано.
абсолютно та же фигня с last и continue :)
(Ответить) (Parent) (Thread)
[User Picture]From: avva
2003-10-08 12:23 pm
Ну вот в 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.

чего я и ожидал, собственно. Но на практике выходит по-другому.
(Ответить) (Parent) (Thread) (Развернуть)
[User Picture]From: saltovski
2003-10-08 12:21 pm
А баг в стандарте или в самом интерпретаторе?
(Ответить) (Thread)
[User Picture]From: avva
2003-10-08 12:24 pm
Баг в программе ;) т.е. эти next; должны были быть return; в любом случае.

Почему next; именно так себя ведёт, соответствует ли это официальному описанию языка, я ещё не разобрался.
(Ответить) (Parent) (Thread)
[User Picture]From: darxeth
2003-10-08 12:35 pm
Весьма странно и интересно. Я нашёл обсуждение (ссылка) этого вопроса в Google groups.
Не знаю насколько полезно, сам сейчас читаю.
(Ответить) (Thread)
From: (Anonymous)
2003-10-08 12:41 pm
Документация нам говорит: "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;
}


Что, конечно, печально
(Ответить) (Thread)
From: (Anonymous)
2003-10-08 12:42 pm
в смысле то, что оно ничего не напечатает
(Ответить) (Parent) (Thread)
[User Picture]From: avva
2003-10-08 12:45 pm
Вот-вот.

Правда, в 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.

Налицо противоречие в документации.
(Ответить) (Parent) (Thread)
[User Picture]From: cema
2003-10-08 12:52 pm
Потому что в Перле не функции, а подпрограммы. :-)

В сторону: что же теперь, все циклы метками помечать?
(Ответить) (Thread)
[User Picture]From: sobaker
2003-10-08 11:27 pm
Т.е. continue выпрыгивает из функции?

Боже, какой дикий бред :)
(Ответить) (Thread)
[User Picture]From: gdy
2003-10-10 08:55 am
Principle of Least Surprise
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.
(Ответить) (Parent) (Thread) (Развернуть)
[User Picture]From: kot_begemot
2003-10-10 06:18 pm
Сто двадцать пятое подтверждение того, что perl не следует использовать помимо элементарного парсинга, в каковой функции он не сильно превосходит awk. Вывод - perl - это никому не нужное извращение дилетанта, пользоваться которым просто опасно.
(Ответить) (Thread)