Thursday, October 1, 2009

ICQ транспорт и kqueue

При настройке icq транспорта (разумеется, имеется в виду py-icqt, так как остальные существующие сейчас проекты либо мертвы, как jit, либо не работают) на FreeBSD возникло странное желание использовать kqueue reactor, так как очевидно, что традиционные select/poll - не лучший выбор при сколько-нибудь большом числе persistent коннекций. Итак, вначале необходимо поставить devel/py-kqueue. Далее, если мы пропишем в конфигурационном файле

<reactor>kqueue</reactor>

то, скорее всего pyicqt начнет кушать 100% CPU. Если посмотреть ktrace, то можно будет увидеть следующее:

25821 python2.5 RET   kevent -1 errno 9 Bad file descriptor
25821 python2.5 CALL  gettimeofday(0xbfbfd8c8,0)
25821 python2.5 RET   gettimeofday 0
25821 python2.5 CALL  gettimeofday(0xbfbfd5b8,0)
25821 python2.5 RET   gettimeofday 0
25821 python2.5 CALL  write(0x2,0x28e70014,0x2ab)
25821 python2.5 GIO   fd 2 wrote 683 bytes
"Traceback (most recent call last):
File "/usr/local/lib/jabber/pyicq/PyICQt.py", line 16, in <module>
main.main()
File "/usr/local/lib/jabber/pyicq/src/main.py", line 473, in main
reactor.run()
File "/usr/local/lib/python2.5/site-packages/twisted/internet/base.py", line 1128, in run
self.mainLoop()
--- <exception caught here> ---
File "/usr/local/lib/python2.5/site-packages/twisted/internet/base.py", line 1140, in mainLoop
self.doIteration(t)
File "/usr/local/lib/python2.5/site-packages/twisted/internet/kqreactor.py", line 189, in doKEvent
l = self._kq.kevent([], len(self._selectables), timeout)
exceptions.OSError: [Errno 9] Bad file descriptor
"
Вспоминаем неприятную особенность kqueue: при форке не наследуюся kqueue дескрипторы. То есть, необходимо выполнить демонизацию самостоятельно. Вторая проблема - неверный расчет тайм-аутов. Фикс ее:


--- kqsyscallmodule.c.bak    2009-10-02 18:24:37.000000000 +0400
+++ kqsyscallmodule.c    2009-10-02 18:26:37.000000000 +0400
@@ -141,7 +141,7 @@
}

statichere PyTypeObject KQEvent_Type = {
-  PyObject_HEAD_INIT(NULL)
+  PyObject_HEAD_INIT(&PyType_Type)
0,                             // ob_size
"KQEvent",                     // tp_name
sizeof(KQEventObject),         // tp_basicsize
@@ -295,14 +295,14 @@

/* Build timespec for timeout */
totimespec.tv_sec = timeout / 1000;
-  totimespec.tv_nsec = (timeout % 1000) * 100000;
+  totimespec.tv_nsec = (timeout % 1000) * 1000000;

// printf("timespec: sec=%d nsec=%d\n", totimespec.tv_sec, totimespec.tv_nsec);

/* Make the call */
-
+  Py_BEGIN_ALLOW_THREADS
gotNumEvents = kevent (self->fd, changelist, haveNumEvents, triggered, wantNumEvents, &totimespec);
-
+  Py_END_ALLOW_THREADS
/* Don't need the input event list anymore, so get rid of it */
free (changelist);

@@ -365,7 +365,7 @@
statichere PyTypeObject KQueue_Type = {
/* The ob_type field must be initialized in the module init function
* to be portable to Windows without using C++. */
-    PyObject_HEAD_INIT(NULL)
+    PyObject_HEAD_INIT(&PyType_Type)
0,            /*ob_size*/
"KQueue",            /*tp_name*/
sizeof(KQueueObject),    /*tp_basicsize*/


Очевидно, что для получения наносекунд, нужно умножить миллисекунды на миллион, а не на сто тысяч.

Вторая проблема - корявая демонизация. Я решил ее исправлением стартового скрипта следующим образом:

--- /usr/local/etc/rc.d/jabber-pyicq-transport.orig    2009-10-02 18:57:06.000000000 +0400
+++ /usr/local/etc/rc.d/jabber-pyicq-transport    2009-10-02 19:29:00.000000000 +0400
@@ -24,8 +24,9 @@
: ${jabber_pyicq_user="ejabberd"}

pidfile="${jabber_pyicq_piddir}/PyICQt.pid"
-command_interpreter="/usr/local/bin/python2.5"
-command="${jabber_pyicq_dir}/PyICQt.py"
-command_args="-b -o pid=${pidfile}"
+python="/usr/local/bin/python2.5"
+procname="${python}"
+command="/usr/sbin/daemon"
+command_args="${python} ${jabber_pyicq_dir}/PyICQt.py -o pid=${pidfile}"

run_rc_command "$1"

То есть, демонизация происходит при помощи /usr/sbin/daemon. Проверка трейса работы pyicqt показала, что после этих действий для работы он использует kevent, причем, использует правильно. Хотя наличие select'ов все равно смущает, но для socket IO он все же использует kqueue.