С момента последнего релиза транспорта набралось несколько минорных обновлений, которым, тем не менее, решил посвятить отдельную заметку.
Во-первых, новая работа с базой – то, ради чего появилась версия 1.1.1. Назойливый баг, появлявшийся через произвольное время раз в несколько дней (или несколько раз в день) с выборкой из таблицы в функции isSent, когда внезапно запрос переставал возвращать данные и ради которого был сделан костылик с try/except – так вот, подобный баг потом начал вылазить и в других местах. Не буду расписывать, сколько времени понадобилось, чтобы понять, что там все-таки происходит и почему.
Однако вторая ошибка подтолкнула мысли в правильном направлении – это было очень похоже на то, когда поначалу пытался сделать обновление лент полностью параллельно и одни запросы в базу накладывались на другие. Повторный разбор логики работы основной части ничего не дал – там все было строго последовательно. Однако включение print’а запросов непосредственно перед отправкой их базе показало, что именно не так. Запросы таки накладывались друг на друга и это ломало работу python-mysqldb. Однако запросы были из разных “веток” PyXMPP, поэтому этот косяк и не был заметен сразу: у “ядра” Component есть несколько handler’ов. Один из них – это то, где я делал основную работу – цикл idle и вызываемый им checkrss со всеми сопутствующими функциями. Однако это не все, есть еще handler, обрабатывающий запросы на поиск, регистрацию и статусы. И если поиском и регистрацией явно никто не пользовался (я пока один пользователь у транспорта), то обработка статусов и стала причиной проблемы: ИНОГДА, во время обновления лент параллельно обновлялся статус у ботов. Для этого делается выборка из базы для того, чтобы узнать, есть ли у бота вообще пользователь и надо ли ставить тот статус. Вот эта выборка и прилетала в базу параллельно с запросами из checkrss.
Как уже выяснилось раньше, у MySQLdb нет пула подключений или чего-то такого. Переписывать все с использованием более фичастой библиотеки тоже не хотелось – да и не нужно. Отдельный “пул” можно было сделать прямо в коде, организовав отдельные подключения для каждой из “веток”. В итоге на сейчас есть 4 подключения, 2 из которых более-менее активны (обновления лент и статусы), а 2 появляются по необходимости – это поиск и регистрация лент. Причем, по-идее, если поиском долго никто не будет пользоваться, то сработает wait_timeout в MySQL, который обычно около 8 часов и подключение завершится (правда, не проверял).
Так или иначе, баг глобально побежден, костыль из isSent убран, теперь все красиво.
Вторая тема по работе с базой вылилась в появление версии 1.1.2. Сделано это было по мотивам https://stackoverflow.com/questions/775296/mysql-parameterized-queries, найденного, в свою очередь, в процессе работы над другим транспортом. Согласно лучшему ответу, стоило использовать “,” вместо “%” для передачи параметров; в том числе это обеспечивало и escaping для запросов. Сходу перейти с одного формата на другой не вышло: была постоянная ругань на то, что передается 3 параметра, а должно быть только 2 (хотя я передавал 2). Только на следующий день стало ясно в чем причина – ругань была не в непосредственном месте передачи запроса, а в враппере вокруг функции, обеспечивающей переподключение к базе при проблемах с отправкой запроса. Функция та действительно принимала только 2 параметра, причем первым был self. Иначе говоря, в варианте с “%” весь запрос с параметрами был одним параметром для функции, а вот в варианте с “,” становился уже двумя. Вместе с self это давало 3 параметра и тут все и падало. Пришлось сделать передачу параметров опциональной отдельной переменной – после этого все заработало.
Пока занимался всем вышеперечисленным, неоднократно думал о том, что как-то многовато запросов. В итоге переделал логику работы в checkrss – нет смысла делать insert как непрочитанной ленты и буквально через один шаг делать апдейт, ставя “единичку” в одной колонке и апдейтя время в другой. Сложно представить ситуацию, когда это пригодится, поэтому сейчас просто по факту отправки сообщения вносится запись в базу. В свою очередь, для тех сообщений, что уже были отправлены, обновление временной метки делается раз в сутки, а не раз в минуту (для активных лент) как раньше.
Вчера немного поразглядывал RSS-транспорт от поляков. Да, мой сейчас выглядит совершеннее. Интересно было, что еще можно почерпнуть из кода PyRSS. Из потенциально реализуемого – обработка сообщений от пользователя. Добавляется отдельный handler, дальше идет разбор тела сообщения. В принципе, потенциально можно будет что-то такое сделать, но пока думаю, что это уже из секции украшательств. Можно сделать, например, такие команды от админа:
- Добавление лент не через интерфейс jabber-клиента. Проще написать что-то в стиле “+ http://address/feed.xml 10 My favourite feed”, чем щелкать кнопки в клиенте. С другой стороны для массового добавления лент мне проще сформировать запрос в базу напрямую.
- purge для таблицы sent – возможно, с какими-то подвариантами (all или только сегодня) – возможность “забыть” про присланные сообщения и отправить их еще раз.
- Принудительное обновление – опять же, с опциями в виде названия конкретной ленты или всех сразу. Фактически, просто сброс счетчика в last_upd.
- Как вариант – возможность редактировать описание и период обновления ленты.
- Соответственно, логичной была бы возможность получить список лент в клиенте.
Пока это просто мысли. Еще из интересного замеченного – в каталоге дополнительных файлов заметил шаблоны для веб-интерфейса к транспорту – как минимум, для отображения списка лент. Идея хорошая, в каких-то больших сетапах может быть полезным.
А вот другим “украшательством” я увлекся и реализовал у себя, что дало версии 1.1.3 и 1.1.4 – статусы. Глянул, как оно в PyRSS – все весьма просто. Схема аналогична отправке сообщения, только для статусов – от кого, кому, какой статус и какое сообщение статуса. В итоге “боты” начали показывать, когда последний раз обновлялись, сколько у них подписчиков, описание ленты и сколько новых сообщений пришло в последнем обновлении. Сделал, отправил на сервер и… Облом. То, что прекрасно работало на тест-машине со старым сервером (ejabberd 2.какой-то-там) не хотело нормально работать с ejabberd’ом на jabbercity.ru 19-й версии. Почему-то бот нормально ставил только статус “chat”, когда приходили новые сообщения, но совершенно не хотел становиться обратно “available”. Делал разные проверки, сравнил код библиотек на сервере и дома – все одинаково. XML статуса в клиенте вообще не приходит. Когда смотрел библиотеку – заметил, что там вообще нет в вариантах опции show значения available, есть None (которую сначала пробовал передавать как значение опции – т.е., с кавычками – понятно, что ничего не вышло). И вот именно с None и стало все работать корректно. Похоже, в более свежем сервере более строгие проверки отправляемых станз – либо в старом сервере был какой-то алиас для available.
На а наутро, так как у меня есть несколько лент с обновлением раз в минуту, понял, что такой статус не сильно информативный и сделал по-другому: теперь для новых заметок создавался список с unixtime’ами времен получения и у ботов в статусе теперь показывается число сообщений за последние сутки. Сам статус тоже ставится исходя из этого числа: для лент с редким обновлением получаем обычный “online”; для “болтливых” – где приходит хотя бы сообщение в сутки – ставится “Готов поболтать” и для тех лент, где была проблема с получением контента ставится “Недоступен”.
Чуть-чуть смущает, правда, приходящий раз в минуту статус от активных лент (там ведь постоянно меняется время обновления), но пока только чуть-чуть 🙂
Update: вот, кстати, косяк небольшой вылез с работой со статусами. Хэндлер, ставящий бота “онлайн” (тот самый, что генерировал SQL-запросы, накладывавшиеся на запросы апдейтов), фактически, просто “зеркало”: получает “онлайн” от клиента – отдает “онлайн” обратно. Вот с текстами статусов в этом месте баг и получился: если у клиента работает автостатус или он еще каким-то образом меняет свой статус, то по возврату обратно “онлайн” у ботов статус (и сообщение) тоже сбрасывается. Для тех лент, где идет частое обновление (раз в минуту, например) это почти незаметно – статус через короткое время все равно будет установлен обратно вместе с нужным сообщением, а вот на остальных это сказывается.
Как вариант – можно расширить функциональность функции presence, мигрировав туда функции из botstatus, чтобы при любой установке статуса бота (как “зеркала” или при вызове со стороны основного треда) устанавливалось нужное сообщение. Или, как вариант – блокировать “зеркало” по каким-то факторам.
Update: на следующий день – новая версия, 1.1.5. Исправлен баг, описанный в предыдущем апдейте, теперь счетчики новых сообщений сделаны глобальными и устанавливаются в том числе в хэндлере presence. Чтобы не дублировать код – сделаны функции, возвращающие текущий статус и статусное сообщение. Изменено отображение статуса, теперь “Chat” используется только в случае, если были сообщения за последний час. Если за последние сутки сообщений не было – ставится “Away”.
[00:27:08] Гм. Придумал фичу для RSS-транспорта – “Приватные ленты”. Те, которые не будут светиться в браузере сервисов Реализовать довольно просто Потенциально еще можно “Защищенные” – кроме приватности, контент будет отдаваться только на JID, с которого была регистрация. Но тут надо подумать о грамотной реализации
[00:27:45]
[00:30:37]
Реализовал “Приватные ленты” меньше, чем за час – так появилась версия 1.2. Вот, кстати, в контексте этой функции к потенциальным командам для админа можно добавить hide/unhide – изменение флага ленты.