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:
		
							parent
							
								
									507bd7b8c6
								
							
						
					
					
						commit
						afbac3d3e6
					
				
					 1 changed files with 125 additions and 57 deletions
				
			
		| 
						 | 
				
			
			@ -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:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue