вівторок, 21 жовтня 2014 р.

Проблема: transceiver session with oserl

https://github.com/iamaleksey/oserl/issues/1

Год назад я написал SMPP-клиента используя библиотеку oserl для моего проекта SMS-рассылок. Тестировал в разных позициях долго и качественно. Но как только дошло до реальных дел, я поймал ошибку. Она оказалась достаточно глубоко зарыта не только в коде oserl, но и в архитектуре.

Суть проблемы в том, что на больших нагрузках в пределах сессии transceiver возникает lock, потому что параллельно вызываются две синхронных функции, что не есть правильно. В качестве временного решения можно обойти ошибку вызвав одну функцию как асинхронную, например обработку deliver_sm, что действительно может быть вполне асинхронным. В деталях я еще буду разбираться, но следующую версию SMPP-клиента надо будет переписать используя альтернативную библиотеку https://github.com/essiene/smpp34.

Спасибо этой ошибке за кардинальное трехнедельное разбирательство в OTP.

неділя, 12 жовтня 2014 р.

Модифицируем AnyEvent::PacketReader

После продолжительных поисков ошибки в Erlang SMPP клиенте, который сам же и написал на основе oserl, ошибка нашлась в свеженаписанном сервере на основе AnyEvent::PacketReader.
Даже не ошибка. Скорее это фича, но меня она не устроила.

Итак, PacketReader замечательно работает, когда надо отвечать на входящие запросы. Но протокол SMPP подразумевает и исходящие запросы в сторону подключившегося клиента тоже.

Что я только не перепробовал. И timer подключал, и пытался генерировать события на основе входящих пакетов. Не получилось ни разу. Код работал, но в определенное время зависал, потому что сокет был намертво заблокирован.

Приходиться доставать рашпиль. our $debug = 1 внутри AnyEvent::PacketReader и очень быстро находится следующий код:
<code>
sub resume {
    my $data = ${shift()};
    if (defined(my $fh = $data->[1])) {
        $data->[2] = AE::io $fh, 0, sub { _read($data) };
    }
}
</code>

Раз тут используется AnyEvent::IO, то что мне мешает на этом же уровне добавить и timer ? Подумал я и сделал следующий ход:

<code>
sub resume {
    my $data = ${shift()};
    if (defined(my $fh = $data->[1])) {
        $data->[2] = AE::io $fh, 0, sub { _read($data) };
        $data->[11] =  AnyEvent->timer ( after => $data->[10], interval => $data->[10], cb => $data->[9] );
    }
}
</code>

data->[10] = 1; data->[9] = sub { warn "Timer ring-ring."; }; data->[11] = undef (будущий watcher, по аналогии к data->[2];

Предварительные тесты показали работоспособность такого варианта. Продолжение следует.