引子
之前clubot使用的pyxmpp2的默认mainloop也就是一个poll的主循环,但是clubot上线后资源占用非常厉害,使用strace跟踪发现clubot在不停的poll,查看pyxmpp2代码发现pyxmpp2的poll在使用超时阻塞时使用最小超时时间,而最小超时时间一直是0,所以会变成一个没有超时的非阻塞poll很浪费资源,不打算更改库代码,所以自己仿照poll的mainloop写了一个更加高效的epoll的mainloop
实现
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
# author : cold
# e-mail : wh_linux@126.com
# date : 13/01/06 10:41:31
# desc : clubot epoll mainloop
#
from __future__ import absolute_import, pision
import select
from pyxmpp2.mainloop.interfaces import handlerready, prepareagain
from pyxmpp2.mainloop.base import mainloopbase
from plugin.util import get_logger
class epollmainloop(mainloopbase):
“”” main event loop based on the epoll() syscall on linux system “””
read_only = (select.epollin | select.epollpri | select.epollhup |
select.epollerr |select.epollet)
read_write = read_only | select.epollout
def __init__(self, settings = none, handlers= none):
self.epoll = select.epoll()
self._handlers = {}
self._unprepared_handlers = {}
self._timeout = none
self._exists_fd = {}
self.logger = get_logger()
mainloopbase.__init__(self, settings, handlers)
return
def _add_io_handler(self, handler):
self._unprepared_handlers[handler] = none
self._configure_io_handler(handler)
def _configure_io_handler(self, handler):
if self.check_events():
return
if handler in self._unprepared_handlers:
old_fileno = self._unprepared_handlers[handler]
prepared = self._prepare_io_handler(handler)
else:
old_fileno = none
prepared = true
fileno = handler.fileno()
if old_fileno is not none and fileno != old_fileno:
del self._handlers[old_fileno]
self._exists.pop(old_fileno, none)
self.epoll.unregister(old_fileno)
if not prepared:
self._unprepared_handlers[handler] = fileno
if not fileno:
return
self._handlers[fileno] = handler
events = 0
if handler.is_readable():
events |= self.read_only
if handler.is_writable():
events |= self.read_write
if events:
if fileno in self._exists_fd:
self.epoll.modify(fileno, events)
else:
self._exists_fd.update({fileno:1})
self.epoll.register(fileno, events)
def _prepare_io_handler(self, handler):
ret = handler.prepare()
if isinstance(ret, handlerready):
del self._unprepared_handlers[handler]
prepared = true
elif isinstance(ret, prepareagain):
if ret.timeout is not none:
if self._timeout is not none:
self._timeout = min(self._timeout, ret.timeout)
else:
self._timeout = ret.timeout
prepared = false
else:
raise typeerror(“unexpected result from prepare()”)
return prepared
def _remove_io_handler(self, handler):
if handler in self._unprepared_handlers:
old_fileno = self._unprepared_handlers[handler]
del self._unprepared_handlers[handler]
else:
old_fileno = handler.fileno()
if old_fileno is not none:
try:
del self._handlers[old_fileno]
self._exists.pop(old_fileno, none)
self.epoll.unregister(old_fileno)
except keyerror:
pass
def loop_iteration(self, timeout = 60):
next_timeout, sources_handled = self._call_timeout_handlers()
if self.check_events():
return
if self._quit:
return sources_handled
for handler in list(self._unprepared_handlers):
self._configure_io_handler(handler)
if self._timeout is not none:
timeout = min(timeout, self._timeout)
if next_timeout is not none:
timeout = min(next_timeout, timeout)
if timeout == 0:
timeout += 1 # 带有超时的非阻塞,解约资源
events = self.epoll.poll(timeout)
for fd, flag in events:
if flag & (select.epollin | select.epollpri | select.epollet):
self._handlers[fd].handle_read()
if flag & (select.epollout|select.epollet):
self._handlers[fd].handle_write()
if flag & (select.epollerr | select.epollet):
self._handlers[fd].handle_err()
if flag & (select.epollhup | select.epollet):
self._handlers[fd].handle_hup()
#if flag & select.epollnval:
#self._handlers[fd].handle_nval()
sources_handled += 1
self._configure_io_handler(self._handlers[fd])
return sources_handled
使用
如何使用新的mainloop?只需在实例化client时传入
mainloop = epollmainloop(settings)
client = client(my_jid, [self, version_provider], settings, mainloop)
这样就会使用epoll作为mainloop
注意
epoll仅仅在linux下支持