Coding standards and documentation improvements in idlequeue.py.

Fix a potential bug of missing an alarm_callback. Separate public from internal API.
This commit is contained in:
Stephan Erb 2008-12-26 13:24:37 +00:00
parent 507bd7b8c6
commit afbac3d3e6
1 changed files with 125 additions and 57 deletions

View File

@ -12,16 +12,32 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
'''
Idlequeues are Gajim's network heartbeat. Transports can be plugged as
idle objects and be informed about possible IO.
'''
import select
import logging
import gobject
log = logging.getLogger('gajim.c.x.idlequeue')
FLAG_WRITE = 20 # write only
FLAG_READ = 19 # read only
FLAG_READ_WRITE = 23 # read and write
FLAG_CLOSE = 16 # wait for close
PENDING_READ = 3 # waiting read event
PENDING_WRITE = 4 # waiting write event
IS_CLOSED = 16 # channel closed
class IdleObject:
''' base class for all idle listeners, these are the methods, which are called from IdleQueue
'''
Idle listener interface. Listed methods are called by IdleQueue.
'''
def __init__(self):
self.fd = -1
self.fd = -1 #: filedescriptor, must be unique for each IdleObject
def pollend(self):
''' called on stream failure '''
@ -39,7 +55,17 @@ class IdleObject:
''' called when timeout happened '''
pass
class IdleQueue:
'''
IdleQueue provide three distinct time based features. Uses select.poll()
1. Alarm timeout: Execute a callback after foo seconds
2. Timeout event: Call read_timeout() of an plugged object if a timeout
has been set, but not removed in time.
3. Check file descriptor of plugged objects for read, write and error
events
'''
def __init__(self):
self.queue = {}
@ -49,18 +75,16 @@ class IdleQueue:
# cb, which are executed after XX sec., alarms are removed automatically
self.alarms = {}
self.init_idle()
self._init_idle()
def init_idle(self):
def _init_idle(self):
''' Hook method for subclassed. Will be called by __init__. '''
self.selector = select.poll()
def remove_timeout(self, fd):
log.info('read timeout removed for fd %s' % fd)
if fd in self.read_timeouts:
del(self.read_timeouts[fd])
def set_alarm(self, alarm_cb, seconds):
''' set up a new alarm, to be called after alarm_cb sec. '''
'''
Sets up a new alarm. alarm_cb will be called after specified seconds.
'''
alarm_time = self.current_time() + seconds
# almost impossible, but in case we have another alarm_cb at this time
if alarm_time in self.alarms:
@ -70,30 +94,50 @@ class IdleQueue:
return alarm_time
def remove_alarm(self, alarm_cb, alarm_time):
''' removes alarm callback alarm_cb scheduled on alarm_time'''
if not self.alarms.has_key(alarm_time): return False
'''
Removes alarm callback alarm_cb scheduled on alarm_time.
Returns True if it was removed sucessfully, otherwise False
'''
if not alarm_time in self.alarms:
return False
i = -1
for i in range(len(self.alarms[alarm_time])):
for i in range(len(self.alarms[alarm_time])):
# let's not modify the list inside the loop
if self.alarms[alarm_time][i] is alarm_cb: break
if i != -1:
if self.alarms[alarm_time][i] is alarm_cb:
break
if i != -1:
del self.alarms[alarm_time][i]
if self.alarms[alarm_time] == []:
if self.alarms[alarm_time] == []:
del self.alarms[alarm_time]
return True
else:
return False
def remove_timeout(self, fd):
''' Removes the read timeout '''
log.info('read timeout removed for fd %s' % fd)
if fd in self.read_timeouts:
del(self.read_timeouts[fd])
def set_read_timeout(self, fd, seconds):
''' set a new timeout, if it is not removed after 'seconds',
then obj.read_timeout() will be called '''
'''
Sets a new timeout. If it is not removed after specified seconds,
obj.read_timeout() will be called.
A filedescriptor fd can have only one timeout.
'''
log.info('read timeout set for fd %s on %s seconds' % (fd, seconds))
timeout = self.current_time() + seconds
self.read_timeouts[fd] = timeout
def check_time_events(self):
def _check_time_events(self):
'''
Execute and remove alarm callbacks and execute read_timeout() for plugged
objects if specified time has ellapsed.
'''
log.info('check time evs')
current_time = self.current_time()
for fd, timeout in self.read_timeouts.items():
if timeout > current_time:
continue
@ -102,15 +146,25 @@ class IdleQueue:
self.queue[fd].read_timeout()
else:
self.remove_timeout(fd)
times = self.alarms.keys()
for alarm_time in times:
if alarm_time > current_time:
break
if self.alarms.has_key(alarm_time):
for cb in self.alarms[alarm_time]: cb()
if self.alarms.has_key(alarm_time): del(self.alarms[alarm_time])
continue
if alarm_time in self.alarms:
for callback in self.alarms[alarm_time]:
callback()
if alarm_time in self.alarms:
del(self.alarms[alarm_time])
def plug_idle(self, obj, writable = True, readable = True):
def plug_idle(self, obj, writable=True, readable=True):
'''
Plug an IdleObject into idlequeue. Filedescriptor fd must be set.
:param obj: the IdleObject
:param writable: True if obj has data to sent
:param readable: True if obj expects data to be reiceived
'''
if obj.fd == -1:
return
if obj.fd in self.queue:
@ -118,48 +172,51 @@ class IdleQueue:
self.queue[obj.fd] = obj
if writable:
if not readable:
flags = 20 # write only
flags = FLAG_WRITE
else:
flags = 23 # both readable and writable
flags = FLAG_READ_WRITE
else:
if readable:
flags = 19 # read only
flags = FLAG_READ
else:
# when we paused a FT, we expect only a close event
flags = 16
self.add_idle(obj.fd, flags)
flags = FLAG_CLOSE
self._add_idle(obj.fd, flags)
def add_idle(self, fd, flags):
def _add_idle(self, fd, flags):
''' Hook method for subclasses, called by plug_idle '''
self.selector.register(fd, flags)
def unplug_idle(self, fd):
''' Removed plugged IdleObject, specified by filedescriptor fd. '''
if fd in self.queue:
del(self.queue[fd])
self.remove_idle(fd)
self._remove_idle(fd)
def current_time(self):
from time import time
return time()
def remove_idle(self, fd):
def _remove_idle(self, fd):
''' Hook method for subclassed, called by unplug_idle '''
self.selector.unregister(fd)
def process_events(self, fd, flags):
def _process_events(self, fd, flags):
obj = self.queue.get(fd)
if obj is None:
self.unplug_idle(fd)
return False
if flags & 3: # waiting read event
if flags & PENDING_READ:
#print 'waiting read on %d, flags are %d' % (fd, flags)
obj.pollin()
return True
elif flags & 4: # waiting write event
elif flags & PENDING_WRITE:
obj.pollout()
return True
elif flags & 16: # closed channel
elif flags & IS_CLOSED:
# io error, don't expect more events
self.remove_timeout(obj.fd)
self.unplug_idle(obj.fd)
@ -167,9 +224,16 @@ class IdleQueue:
return False
def process(self):
'''
Process idlequeue. Check for any pending timeout or alarm events.
Call IdleObjects on possible and requested read, write and error events
on their file descriptors.
Call this in regular intervals.
'''
if not self.queue:
# check for timeouts/alert also when there are no active fds
self.check_time_events()
self._check_time_events()
return True
try:
waiting_descriptors = self.selector.poll(0)
@ -178,25 +242,27 @@ class IdleQueue:
if e[0] != 4: # interrupt
raise
for fd, flags in waiting_descriptors:
self.process_events(fd, flags)
self.check_time_events()
self._process_events(fd, flags)
self._check_time_events()
return True
class SelectIdleQueue(IdleQueue):
'''
Extends IdleQueue to use select.select() for polling
This class exisists for the sake of gtk2.8 on windows, which
doesn't seem to support io_add_watch properly (yet)
'''
# TODO: remove this class and its reference gajim.py, when win-gtk2.8 is stable
def init_idle(self):
''' this method is called at the end of class constructor.
Creates a dict, which maps file/pipe/sock descriptor to glib event id'''
def _init_idle(self):
'''
Creates a dict, which maps file/pipe/sock descriptor to glib event id
'''
self.read_fds = {}
self.write_fds = {}
self.error_fds = {}
def add_idle(self, fd, flags):
def _add_idle(self, fd, flags):
''' this method is called when we plug a new idle object.
Remove descriptor to read/write/error lists, according flags
'''
@ -206,7 +272,7 @@ class SelectIdleQueue(IdleQueue):
self.write_fds[fd] = fd
self.error_fds[fd] = fd
def remove_idle(self, fd):
def _remove_idle(self, fd):
''' this method is called when we unplug a new idle object.
Remove descriptor from read/write/error lists
'''
@ -219,7 +285,7 @@ class SelectIdleQueue(IdleQueue):
def process(self):
if not self.write_fds and not self.read_fds:
self.check_time_events()
self._check_time_events()
return True
try:
waiting_descriptors = select.select(self.read_fds.keys(),
@ -240,24 +306,26 @@ class SelectIdleQueue(IdleQueue):
q = self.queue.get(fd)
if q:
q.pollend()
self.check_time_events()
self._check_time_events()
return True
class GlibIdleQueue(IdleQueue):
'''
Extends IdleQueue to use glib io_add_wath, instead of select/poll
In another, `non gui' implementation of Gajim IdleQueue can be used safetly.
In another 'non gui' implementation of Gajim IdleQueue can be used safetly.
'''
def init_idle(self):
''' this method is called at the end of class constructor.
Creates a dict, which maps file/pipe/sock descriptor to glib event id'''
def _init_idle(self):
'''
Creates a dict, which maps file/pipe/sock descriptor to glib event id
'''
self.events = {}
# time() is already called in glib, we just get the last value
# overrides IdleQueue.current_time()
self.current_time = gobject.get_current_time
def add_idle(self, fd, flags):
def _add_idle(self, fd, flags):
''' this method is called when we plug a new idle object.
Start listening for events from fd
'''
@ -268,13 +336,13 @@ class GlibIdleQueue(IdleQueue):
def _process_events(self, fd, flags):
try:
return self.process_events(fd, flags)
return IdleQueue._process_events(self, fd, flags)
except Exception:
self.remove_idle(fd)
self.add_idle(fd, flags)
self._remove_idle(fd)
self._add_idle(fd, flags)
raise
def remove_idle(self, fd):
def _remove_idle(self, fd):
''' this method is called when we unplug a new idle object.
Stop listening for events from fd
'''
@ -284,7 +352,7 @@ class GlibIdleQueue(IdleQueue):
del(self.events[fd])
def process(self):
self.check_time_events()
self._check_time_events()
# vim: se ts=3: