示例#1
0
文件: queue.py 项目: reidlindsay/wins
    def __init__(self, priority=False, monitorQ=False, \
                 unitName="packet", capacity="unbounded", \
                 initialBuffered=None, **kwargs):
        """Constructor.

        :param priority:  Boolean; if True, use priority queueing.
        :param monitorQ:  Boolean; if True, support `enQ`, `deQ`, and `drp`.
        :param unitName:  Description of units stored in `Queue`.
        :param capacity:  Capacity of underlying Store [default='unbounded'].
        :param initialBuffered: Initialize list of buffered objects.
        :param kwargs:    Keywords passed to `Traceable` constructors.
        """
        # check that initialBuffered is properly formatted
        if initialBuffered and priority:
            isPrio = all([isinstance(s,tuple) for s in initialBuffered] )
            if not isPrio: initialBuffered = [(p, 0) for p in initialBuffered]
        # call constructors
        Store.__init__(self, unitName=unitName, capacity=capacity, \
                       initialBuffered=initialBuffered, \
                       putQType=PriorityQ, getQType=PriorityQ)
        Traceable.__init__(self, **kwargs)
        # set other parameters
        self.tracename = self.tracename + "%d"%(self.uid)
        self.__priority = priority
        self.monitorQ = monitorQ
        self.enQ = SimEvent(name=self.name+".enQ")
        self.deQ = SimEvent(name=self.name+".deQ")
        self.drp = SimEvent(name=self.name+".drp")
        self.__dummy = SimEvent(name=self.name+".dummy")
        # set up Queue for priority
        if self.priority:
            self.theBuffer = _PriorityList(self.theBuffer)
        self.addSort(None) # setup addSort for priority queueing
示例#2
0
 def __init__(self):
     Process.__init__(self)
     self._signals = {
         'kill' : SimEvent('kill'),
         'finish' : SimEvent('finish'),
     }
     self._finished = False
 def __init__(self, dataaccess, discovery, sim):
     super(Provider, self).__init__(sim = sim)
     
     self.discovery = discovery
     self.discovery.add_changes_observer(self)
     if dataaccess is not None: # it should be None just for testing purposes!
         self.clue_manager = ClueManager(dataaccess)
     
     self.__stop = False
     self.wp_node_name = None
     self.connector = None
     
     self.externalCondition = SimEvent(name="external_condition_on_%s"%(self.name), sim = sim)
     self.on_external_condition = False # both when a response from a request is received or when the WP changes!
     self.clueChanged = SimEvent(name="clue_change_on_%s"%(self.name), sim = sim)
     self.stopProvider = SimEvent(name="stop_provider_%s"%(self.name), sim = sim)
     self.timer = None
     
     self.last_contribution_to_aggregated_clue = Version(-1, -1)
     self.last_wp_notification = WPRequestNotifier("super_fake_node", self.sim)
     # When we have to detect the need of updating our clues in the WP:
     #     have we received a new version confirming that the WP received it correctly?
     # Once we decide to send our clue a WP needs to acknowledge that has received it.
     # Even if it is not the same WP.
     # Otherwise it may happen that a new WP is initialized from a version higher than
     # the one this node has contributed without having its information.
     self.pending_update = False
示例#4
0
文件: csphy.py 项目: reidlindsay/wins
 def __init__(self, **kwargs):
     """Constructor."""
     self.__csmode = CSPHY_IDLE
     self.csbusy = SimEvent(name="csbusy")
     self.csidle = SimEvent(name="csidle")
     PHY.__init__(self, **kwargs)
     # rename events
     self.csbusy.name = "%s(%s).csbusy"%(self.name, self.uid)
     self.csidle.name = "%s(%s).csidle"%(self.name, self.uid)
     # monitor events -> keep up to date
     monitor_events(self.csbusy, self.csidle)
class Timer(Process):
    def __init__(self, waitUntil=10000.0, name="timer", sim=None):
        super(Timer, self).__init__(name=name, sim=sim)
        self.__timeout = waitUntil
        self.event = SimEvent(name="timer_event", sim=sim)
        self.ended = False
        
    def wait(self):
        yield hold, self, self.__timeout
        self.ended = True
        self.event.signal()
class ContinuousQuerier(Process):
    """Check Section 5.2"""
    
    # the interval between the first two queries MUST be at least one second
    MINIMUM_FIRST_WAIT = 1000
    MINIMUM_INCREMENT_RATE = 2
    
    FIRST_WAIT = MINIMUM_FIRST_WAIT + 4000
    INCREMENT_RATE = MINIMUM_INCREMENT_RATE * 2
    
    def __init__(self, subquery, sim, sender=None):
        super(ContinuousQuerier, self).__init__(sim=sim)
        self.sender = sender
        # if there is a unique response, it should stop querying
        # I will assume that there is not unique response (asking for PTR records)
        self.subquery = subquery
        self._random = Random()
        self.stopped = False
        self.__stop = SimEvent(name="stop_continuous_querier", sim=sim)
    
    def query_continuously(self):
        # SHOULD also delay the first query of the series
        # by a randomly-chosen amount in the range 20-120ms.
        
        twait = 20 + self._random.random()*100
        timer = Timer(waitUntil=twait, sim=self.sim)
        self.sim.activate(timer, timer.wait())
        yield waitevent, self, (timer.event, self.__stop,)
        self.sender.send_query(self.subquery, to_node=self.sender.node_id) # joining a network
        
        # the interval between the first two queries MUST be at least one second
        twait = ContinuousQuerier.FIRST_WAIT
        while not self.stopped:
            timer = Timer(waitUntil=twait, sim=self.sim)
            self.sim.activate(timer, timer.wait())
            yield waitevent, self, (timer.event, self.__stop,)
            self.sender.send_query(self.subquery) # subsequent queries
            
            if twait!=3600000:
                # the intervals between successive queries MUST increase by at least a factor of two
                twait = twait * ContinuousQuerier.INCREMENT_RATE
                # When the interval between queries reaches or exceeds 60 minutes
                # a querier MAY cap the interval to a maximum of 60 minutes
                # and perform subsequent queries at a steady-state rate of one query per hour
                if twait>3600000:
                    twait = 3600000 # 1h
    
    def stop(self):
        self.stopped = True
        self.__stop.signal()
    
    def reset(self):
        self.stopped = False
示例#7
0
文件: dsr.py 项目: reidlindsay/wins
 def __init__(self, *args, **kwargs):
     """Constructor."""
     # set up parameters
     self.__mac = None
     self.rreqrate = None
     self.datarate = None
     self.maintbuffer = None
     # additional parameters for signalling
     self.sndrreq = SimEvent()
     self.drprreq = SimEvent()
     self.finrreq = SimEvent()
     self.sndfail = SimEvent()
     # call base constructor
     Routing.__init__(self, *args, **kwargs)
示例#8
0
class Alarm(Process):
    @classmethod
    def setOnetime(cls, delay, name=None, at=0, drift=('fixed', 0)):
        tm = Alarm(delay, name, drift=drift)
        activate(tm, tm.onetime())
        return tm.event

    @classmethod
    def setPeriodic(cls, interval, name=None, at=0,
                    until=infinite, drift=('fixed', 0)):
        tm = Alarm(interval, name, until, drift)
        activate(tm, tm.loop(), at=at)
        return tm.event

    def __init__(self, interval, name=None,
                 until=infinite, drift=('fixed', 0)):
        Process.__init__(self)
        self.interval = interval
        if name is not None:
            eventname = name
        else:
            eventname = "a_SimEvent"
        self.event = SimEvent(eventname)
        self.until = until
        try:
            key, mean, cfg = drift
        except ValueError:
            key, mean = drift
            cfg = {}
        lb = cfg.get('lb', 0); ub = cfg.get('ub', interval)
        if lb < 0: raise ValueError('drift lb = %s >= 0' %lb)
        if ub > interval:
            raise ValueError('drift ub = %s < %s = interval' %interval)
        cfg['lb'] = lb; cfg['ub'] = ub
        self.rgen = RandInterval.get(key, mean, cfg)

    def onetime(self):
        drift = self.rgen.next()
        yield hold, self, self.interval + drift
        self.event.signal()

    def loop(self):
        left = 0
        while (self.until < 0) or (now() < self.until):
            yield hold, self, left
            drift = self.rgen.next()
            yield hold, self, drift
            self.event.signal()
            left = self.interval - drift
示例#9
0
 def __init__(self, cnode, index, configs):
     StorageNode.__init__(self, cnode, index, configs)
     #txns that are not yet granted locks, in FCFS order
     self.lockingQueue = []
     self.waitingSet = set([])
     self.nextEvent = SimEvent()
     self.ts = 0
 def __init__(self, sim, sender=None):
     super(Responder, self).__init__(sim=sim)
     self._random = Random()
     self.__new_query_queued = SimEvent(name="new_query_queued", sim=sim)
     self.local_records = {} # key: record, value: last time advertised
     self.queued_queries = [] # tuple: ( when to answer, query )
     self.sender = sender
示例#11
0
文件: dcf.py 项目: reidlindsay/wins
    def __init__(self, cwmin=None, cwmax=None, retrylimit=None, \
                       usecsma=False, **kwargs):
        """Constructor.

        :param cwmin: Minimum contention window size.
        :param cwmax: Maximum contention window size.
        :param retrylimit: Maximum number of retries allowed.
        :param usecsma: Boolean flag; if true, use CSMA/CA without RTS-CTS
                        reservation messages.
        :param kwargs: Additional keywords passed to `configure()`.

        The default parameters are specified by the class.
        """
        if cwmin is None: cwmin = self.__class__.cwmin
        if cwmax is None: cwmax = self.__class__.cwmax
        if retrylimit is None: retrylimit = self.__class__.retrylimit
        # timing parameters
        self.sifs, self.slottime = None, None
        self.cwmin, self.cwmax   = cwmin, cwmax
        self.cslot = None
        # events and other members
        self.datatosend = None
        self.retrycount = None
        self.retrylimit = retrylimit
        self.usecsma = usecsma
        self.rxdata = SimEvent(name=".rxdata")
        self._ctsduration = None
        self._ackduration = None
        # call CSMAC constructor
        CSMAC.__init__(self, **kwargs)
        self.rxdata.name = "%s.rxdata"%(self.name)
示例#12
0
    def __init__(self, name, sim, task):
        Process.__init__(self, name=name, sim=sim)

        # number of the action
        self.q = 0

        # corresonding pycpa task
        self.task = task

        # the signal used to wake up the activation event
        self.signal_event = SimEvent(sim=sim)

        # workload left to consume
        self.workload = task.wcet

        # active segments of the execution of the form: [(0,1), (3,9)]
        self.exec_windows = list()

        # last recent start of a execution segment
        self.recent_window_start = None

        # actual response time
        self.response_time = 0

        # start time
        self.start_time = 0

        # finishing time
        self.finish_time = 0
示例#13
0
 def __init__(self, system, ID, configs):
     IDable.__init__(self, 'zone%s/cn' % ID)
     Thread.__init__(self)
     RTI.__init__(self, self.ID)
     self.logger = logging.getLogger(self.__class__.__name__)
     self.system = system
     self.snodes = []
     self.configs = configs
     self.groupLocations = {}
     self.txnsRunning = set([])
     self.shouldClose = False
     self.closeEvent = SimEvent()
     #paxos entities
     self.paxosPRunner = None
     self.paxosAcceptor = None
     self.paxosLearner = None
示例#14
0
 def configure(self, **kwargs):
     """Initialize `timer` to None and create monitor."""
     self.__timer = None
     self.__wakeup = SimEvent(name=self.name+".wake")
     mon = self.newchild("mon", FSM, tracename=self.tracename+".MON")
     #mon.goto(self.MON)
     mon.goto(self.MONIDLE)
 def __init__(self, sim, record_observer=None):
     super(Cache, self).__init__(sim=sim)
     self.record_observer = record_observer
     self.__new_record_cached = SimEvent(name="new_record_cached", sim=sim)
     self._random = Random()
     self.pending_events = [] # tuples with the form (when, action, record)
     self.records = [] # cached records
示例#16
0
 def __init__(self, **kwargs):
     """Constructor."""
     self.halduplex = None
     self.__ifstate = CHANNELIF_RX
     # set up events and buffer
     self.txdata = SimEvent(name="txdata")
     self.txdone = SimEvent(name="txdone")
     self.rxdata = SimEvent(name="rxdata")
     self.rxdone = SimEvent(name="rxdone")
     self.rxbuffer = []
     Element.__init__(self, **kwargs)
     # rename events
     self.txdata.name = "%s(%s).txdata"%(self.name, self.uid)
     self.txdone.name = "%s(%s).txdone"%(self.name, self.uid)
     self.rxdata.name = "%s(%s).rxdata"%(self.name, self.uid)
     self.rxdone.name = "%s(%s).rxdone"%(self.name, self.uid)
示例#17
0
文件: arf.py 项目: reidlindsay/wins
 def __init__(self, usecsma=True, **kwargs):
     """Constructor."""
     # create ARF parameters
     self.nsuccess = None
     self.nfailure = None
     self.timeout  = None
     self.ackcount = {}
     self.arftimer = {}
     self.probation = {}
     self._rates = None
     # create ARF events
     self.dropack = SimEvent()
     self.recvack = SimEvent()
     DCF.__init__(self, usecsma=usecsma, **kwargs)
     # update event names
     self.dropack.name = "%s%s"%(self.name, ".dropack")
     self.recvack.name = "%s%s"%(self.name, ".recvack")
示例#18
0
文件: fsm.py 项目: reidlindsay/wins
    def __init__(self, duration, start=False, initstate=None, **kwargs):
        """Constructor.

        :param duration: Time for which `Timer` should run.
        :param start: Boolean; if true, `start()` immediately.
        :param initstate: Depricated (do not use).
        :param kwargs: Keywords passed to `FSM` constructor.
        """
        assert (duration>0), "[TIMER]: Cannot simulate non-positive duration!"
        FSM.__init__(self, start=False, initstate=self.RUN, **kwargs)
        self.__tpassed = 0
        self.__duration  = duration
        self.__ctrlQ = Queue()
        self.__tic   = None
        self.done = SimEvent(name=self.name+".done")
        self.kill = SimEvent(name=self.name+".kill")
        if start: self.start()
示例#19
0
 def block(self, thread, state):
     """Block a thread for this object."""
     assert thread not in self.blockedThreads, \
             '%s, %s, (%s)' %(self.ID, thread.ID, ','.join(
                 [str(t.ID) for t in self.blockedThreads]))
     event = SimEvent()
     self.blockQueue.append(thread)
     self.blockedThreads[thread] = (state, event)
     return event
 def __init__(self, subquery, sim, sender=None):
     super(ContinuousQuerier, self).__init__(sim=sim)
     self.sender = sender
     # if there is a unique response, it should stop querying
     # I will assume that there is not unique response (asking for PTR records)
     self.subquery = subquery
     self._random = Random()
     self.stopped = False
     self.__stop = SimEvent(name="stop_continuous_querier", sim=sim)
示例#21
0
 def run(self, aseed):
     """ PEM """
     seed(aseed)
     self.dooropen = SimEvent("Door Open", sim=self)
     self.counter = Resource(1, name="Clerk", sim=self)
     doorman = Doorman(sim=self)
     self.activate(doorman, doorman.openthedoor())
     source = Source(sim=self)
     self.activate(source, source.generate(number=5, rate=0.1), at=0.0)
     self.simulate(until=maxTime)
示例#22
0
文件: agent.py 项目: reidlindsay/wins
 def __init__(self, *args, **kwargs):
     """Constructor."""
     self._seqno, self._mode = 0, None
     self._payload = None
     # set parameters
     self.delay = None
     self.nsent, self.nrcvd = 0, 0
     self.dest, self.plen = None, None
     self.recv, self.send = SimEvent(), SimEvent()
     self.recvbuffer = {}
     Element.__init__(self, *args, **kwargs)
示例#23
0
    def __init__(self, sim, name="SPP", tasks=list()):

        assert sim != None
        Process.__init__(self, name=name, sim=sim)
        self.tasks = tasks
        self.pending = list()

        # list of simtasks
        self.simtasks = list()

        self.arrival_event = SimEvent('Arrival Event', sim=sim)
示例#24
0
 def getWaitMsgEvents(self, tags):
     events = []
     if not (isinstance(tags, list) or isinstance(tags, tuple)):
         tags  = (tags, )
     for tag in tags:
         if tag not in self.rtiNotifiers:
             event = SimEvent()
             self.rtiNotifiers[tag] = event
         event = self.rtiNotifiers[tag]
         events.append(event)
     return events
class NewWPDetector(Process):
    
    def __init__(self, mdns_instance, sim):
        super(NewWPDetector, self).__init__( sim = sim )
        self.instance = mdns_instance
        self.last_wp_name = None
        self.__new_record_update = SimEvent(name="new_record_updated", sim=sim)
    
    def check_new_wps(self):
        while True:
            wp_r = self.instance.get_whitepage_record()
            if wp_r is not None:
                new_name = wp_r.node_name
                if self.last_wp_name != new_name:
                    self.instance.notify_whitepage_changed()
                    self.last_wp_name = new_name
            yield waitevent, self, (self.__new_record_update,)
    
    def notify_txt_record(self, record):
        if "iw" in record.keyvalues:
            self.__new_record_update.signal()
示例#26
0
 def __init__(self, cnode, ID, configs):
     IDable.__init__(self, '%s/sn%s' % (cnode.ID.split('/')[0], ID))
     Thread.__init__(self)
     RTI.__init__(self, self.ID)
     self.logger = logging.getLogger(self.__class__.__name__)
     self.system = cnode.system
     self.configs = configs
     self.cnode = cnode
     self.maxNumTxns = configs.get('max.num.txns.per.storage.node', 1024)
     self.pool = Resource(self.maxNumTxns, name='pool', unitName='thread')
     self.groups = {}  #{gid : group}
     self.newTxns = []
     self.txnsRunning = set([])
     self.shouldClose = False
     self.monitor = Profiler.getMonitor(self.ID)
     self.M_POOL_WAIT_PREFIX = '%s.pool.wait' % self.ID
     self.M_TXN_RUN_PREFIX = '%s.txn.run' % self.ID
     self.M_NUM_TXNS_RUN_KEY = '%s.num.txns' % self.ID
     self.runningThreads = set([])
     self.closeEvent = SimEvent()
     self.newTxnEvent = SimEvent()
示例#27
0
 def __init__(self, interval, name=None,
              until=infinite, drift=('fixed', 0)):
     Process.__init__(self)
     self.interval = interval
     if name is not None:
         eventname = name
     else:
         eventname = "a_SimEvent"
     self.event = SimEvent(eventname)
     self.until = until
     try:
         key, mean, cfg = drift
     except ValueError:
         key, mean = drift
         cfg = {}
     lb = cfg.get('lb', 0); ub = cfg.get('ub', interval)
     if lb < 0: raise ValueError('drift lb = %s >= 0' %lb)
     if ub > interval:
         raise ValueError('drift ub = %s < %s = interval' %interval)
     cfg['lb'] = lb; cfg['ub'] = ub
     self.rgen = RandInterval.get(key, mean, cfg)
示例#28
0
 def __init__(self, cnode, ID, configs):
     IDable.__init__(self, '%s/sn%s'%(cnode.ID.split('/')[0], ID))
     Thread.__init__(self)
     RTI.__init__(self, self.ID)
     self.logger = logging.getLogger(self.__class__.__name__)
     self.system = cnode.system
     self.configs = configs
     self.cnode = cnode
     self.maxNumTxns = configs.get('max.num.txns.per.storage.node', 1024)
     self.pool = Resource(self.maxNumTxns, name='pool', unitName='thread')
     self.groups = {}    #{gid : group}
     self.newTxns = []
     self.txnsRunning = set([])
     self.shouldClose = False
     self.monitor = Profiler.getMonitor(self.ID)
     self.M_POOL_WAIT_PREFIX = '%s.pool.wait' %self.ID
     self.M_TXN_RUN_PREFIX = '%s.txn.run' %self.ID
     self.M_NUM_TXNS_RUN_KEY = '%s.num.txns'%self.ID
     self.runningThreads = set([])
     self.closeEvent = SimEvent()
     self.newTxnEvent = SimEvent()
 def __init__(self, actionNode, destinationNodes, url, data=None, waitUntil=10000.0, name="request", sim=None):
     super(RequestInstance, self).__init__(name=name, sim=sim)
     self.name += " (from=%s, url=%s)"%(actionNode.name, url)
     self.__actionNode = weakref.proxy(actionNode) #weakref.ref(actionNode)
     self.__destinationNodes = weakref.WeakSet(destinationNodes) # tuple with all the nodes to be requested
     self.url = url # accessible
     self.__data = data
     
     self.requestInit = {} # requestInit[reqId1] = now(), requestInit[reqId2] = now()
     self.responses = [] # accessible
     self.__maxWaitingTime = waitUntil
     self.nodeNamesByReqId = {} # used in the gossiping mechanism with the gossiping requests
     
     self.__newResponseReceived = SimEvent(name="request_response_for_%s"%(self.name), sim=sim)
     self.__observers = weakref.WeakSet()
示例#30
0
    def __init__(self, sim, name="SPNP", tasks=list()):

        assert sim != None
        Process.__init__(self, name=name, sim=sim)

        # list of pending activations
        self.pending = list()

        # list of blocker activations (usually one lower priority activation)
        self.blockers = list()

        # list of simtasks
        self.simtasks = list()

        # signals a new activation
        self.arrival_event = SimEvent('Arrival Event', sim=sim)
示例#31
0
 def __init__(self, system, ID, configs):
     IDable.__init__(self, 'zone%s/cn'%ID)
     Thread.__init__(self)
     RTI.__init__(self, self.ID)
     self.logger = logging.getLogger(self.__class__.__name__)
     self.system = system
     self.snodes = []
     self.configs = configs
     self.groupLocations = {}
     self.txnsRunning = set([])
     self.shouldClose = False
     self.closeEvent = SimEvent()
     #paxos entities
     self.paxosPRunner = None
     self.paxosAcceptor = None
     self.paxosLearner = None
示例#32
0
class ClientNode(IDable, Thread, RTI):
    """Base client node.

    Base client node accepts txn requests and dispatch them to storage nodes.
    They are also hosts of paxos protocol entities.

    """
    def __init__(self, system, ID, configs):
        IDable.__init__(self, 'zone%s/cn' % ID)
        Thread.__init__(self)
        RTI.__init__(self, self.ID)
        self.logger = logging.getLogger(self.__class__.__name__)
        self.system = system
        self.snodes = []
        self.configs = configs
        self.groupLocations = {}
        self.txnsRunning = set([])
        self.shouldClose = False
        self.closeEvent = SimEvent()
        #paxos entities
        self.paxosPRunner = None
        self.paxosAcceptor = None
        self.paxosLearner = None

    def addSNodes(self, snodes):
        self.snodes.extend(snodes)

    #notify new txn arrive, called by the system
    def onTxnArrive(self, txn):
        self.system.onTxnArrive(txn)
        self.txnsRunning.add(txn)
        self.dispatchTxn(txn)

    #notify new txn depart, called by the storage nodes
    def onTxnDepart(self, txn):
        if txn in self.txnsRunning:
            self.txnsRunning.remove(txn)
            self.system.onTxnDepart(txn)

    def dispatchTxn(self, txn):
        #just basic load balance
        hosts = self.getTxnHosts(txn)
        bestHost = iter(hosts).next()
        leastLoad = bestHost.load
        for host in hosts:
            if host.load < leastLoad:
                leastLoad = host.load
                bestHost = host
        bestHost.onTxnArrive(txn)
        self.logger.debug('%s dispatch %s to %s at %s' %
                          (self.ID, txn.ID, bestHost, now()))
        return bestHost

    def getTxnHosts(self, txn):
        hosts = set([])
        for gid in txn.gids:
            hosts.add(self.groupLocations[gid])
        return hosts

    def close(self):
        self.logger.info('Closing %s at %s' % (self, now()))
        self.shouldClose = True
        self.closeEvent.signal()

    def _close(self):
        ##periodically check if we still have txn running
        #while True:
        #    yield hold, self, 100
        #    if len(self.txnsRunning) == 0:
        #        break
        for snode in self.groupLocations.values():
            snode.close()
        for snode in self.groupLocations.values():
            if not snode.isFinished():
                yield waitevent, self, snode.finish
        try:
            self.paxosPRunner.close()
            self.paxosAcceptor.close()
            self.paxosLearner.close()
        except:
            pass

    def run(self):
        while not self.shouldClose:
            yield waitevent, self, self.closeEvent
            if self.shouldClose:
                for step in self._close():
                    yield step
示例#33
0
class StorageNode(IDable, Thread, RTI):
    """Base storage node."""
    def __init__(self, cnode, ID, configs):
        IDable.__init__(self, '%s/sn%s'%(cnode.ID.split('/')[0], ID))
        Thread.__init__(self)
        RTI.__init__(self, self.ID)
        self.logger = logging.getLogger(self.__class__.__name__)
        self.system = cnode.system
        self.configs = configs
        self.cnode = cnode
        self.maxNumTxns = configs.get('max.num.txns.per.storage.node', 1024)
        self.pool = Resource(self.maxNumTxns, name='pool', unitName='thread')
        self.groups = {}    #{gid : group}
        self.newTxns = []
        self.txnsRunning = set([])
        self.shouldClose = False
        self.monitor = Profiler.getMonitor(self.ID)
        self.M_POOL_WAIT_PREFIX = '%s.pool.wait' %self.ID
        self.M_TXN_RUN_PREFIX = '%s.txn.run' %self.ID
        self.M_NUM_TXNS_RUN_KEY = '%s.num.txns'%self.ID
        self.runningThreads = set([])
        self.closeEvent = SimEvent()
        self.newTxnEvent = SimEvent()

    @property
    def load(self):
        return len(self.txnsRunning) + len(self.newTxns)

    def close(self):
        self.logger.info('Closing %s at %s'%(self, now()))
        self.shouldClose = True
        self.closeEvent.signal()

    def onTxnArrive(self, txn):
        self.newTxns.append(txn)
        self.newTxnEvent.signal()

    def onTxnsArrive(self, txns):
        self.newTxns.extend(txns)
        self.newTxnEvent.signal()

    def newTxnRunner(self, txn):
        class DefaultTxnRunner(Thread):
            def __init__(self, snode, txn):
                Thread.__init__(self)
                self.snode = snode
                self.txn = txn
                self.logger = logging.getLogger(self.__class__.__name__)

            def run(self):
                self.logger.debug('Running transaction %s at %s'
                                  %(txn.ID, now()))
                yield hold, self, RandInterval.get('expo', 100).next()
        return DefaultTxnRunner(self, txn)

    class TxnStarter(Thread):
        def __init__(self, snode, txn):
            Thread.__init__(self)
            self.snode = snode
            self.txn = txn

        def run(self):
            #add self and txn to snode
            self.snode.runningThreads.add(self)
            #wait for pool thread resource if necessary
            #self.snode.logger.debug(
            #    '%s start txn=%s, running=%s, outstanding=%s'
            #    %(self.snode, self.txn.ID,
            #      '(%s)'%(','.join([t.ID for t in self.snode.txnsRunning])),
            #      '(%s)'%(','.join([t.ID for t in self.snode.newTxns]))
            #     ))
            self.snode.monitor.start(
                '%s.%s'%(self.snode.M_POOL_WAIT_PREFIX, self.txn.ID))
            yield request, self, self.snode.pool
            self.snode.monitor.stop(
                '%s.%s'%(self.snode.M_POOL_WAIT_PREFIX, self.txn.ID))
            #start runner and wait for it to finish
            thread = self.snode.newTxnRunner(self.txn)
            assert self.txn not in self.snode.txnsRunning, \
                    '%s already started txn %s'%(self.snode, self.txn)
            self.snode.txnsRunning.add(self.txn)
            self.snode.monitor.observe(self.snode.M_NUM_TXNS_RUN_KEY,
                                       len(self.snode.txnsRunning))
            self.snode.monitor.start(
                '%s.%s'%(self.snode.M_TXN_RUN_PREFIX, self.txn.ID))
            thread.start()
            yield waitevent, self, thread.finish
            self.snode.monitor.stop(
                '%s.%s'%(self.snode.M_TXN_RUN_PREFIX, self.txn.ID))
            yield release, self, self.snode.pool
            #clean up
            self.snode.txnsRunning.remove(self.txn)
            self.snode.runningThreads.remove(self)
            self.snode.cnode.onTxnDepart(self.txn)

    def run(self):
        #the big while loop
        while True:
            yield waitevent, self, self.newTxnEvent
            while len(self.newTxns) > 0:
                #pop from new txn to running txn
                txn = self.newTxns.pop(0)
                #start
                thread = StorageNode.TxnStarter(self, txn)
                thread.start()
示例#34
0
class ClientNode(IDable, Thread, RTI):
    """Base client node.

    Base client node accepts txn requests and dispatch them to storage nodes.
    They are also hosts of paxos protocol entities.

    """
    def __init__(self, system, ID, configs):
        IDable.__init__(self, 'zone%s/cn'%ID)
        Thread.__init__(self)
        RTI.__init__(self, self.ID)
        self.logger = logging.getLogger(self.__class__.__name__)
        self.system = system
        self.snodes = []
        self.configs = configs
        self.groupLocations = {}
        self.txnsRunning = set([])
        self.shouldClose = False
        self.closeEvent = SimEvent()
        #paxos entities
        self.paxosPRunner = None
        self.paxosAcceptor = None
        self.paxosLearner = None

    def addSNodes(self, snodes):
        self.snodes.extend(snodes)

    #notify new txn arrive, called by the system
    def onTxnArrive(self, txn):
        self.system.onTxnArrive(txn)
        self.txnsRunning.add(txn)
        self.dispatchTxn(txn)

    #notify new txn depart, called by the storage nodes
    def onTxnDepart(self, txn):
        if txn in self.txnsRunning:
            self.txnsRunning.remove(txn)
            self.system.onTxnDepart(txn)

    def dispatchTxn(self, txn):
        #just basic load balance
        hosts = self.getTxnHosts(txn)
        bestHost = iter(hosts).next()
        leastLoad = bestHost.load
        for host in hosts:
            if host.load < leastLoad:
                leastLoad = host.load
                bestHost = host
        bestHost.onTxnArrive(txn)
        self.logger.debug('%s dispatch %s to %s at %s'
                          %(self.ID, txn.ID, bestHost, now()))
        return bestHost

    def getTxnHosts(self, txn):
        hosts = set([])
        for gid in txn.gids:
            hosts.add(self.groupLocations[gid])
        return hosts

    def close(self):
        self.logger.info('Closing %s at %s'%(self, now()))
        self.shouldClose = True
        self.closeEvent.signal()

    def _close(self):
        ##periodically check if we still have txn running
        #while True:
        #    yield hold, self, 100
        #    if len(self.txnsRunning) == 0:
        #        break
        for snode in self.groupLocations.values():
            snode.close()
        for snode in self.groupLocations.values():
            if not snode.isFinished():
                yield waitevent, self, snode.finish
        try:
            self.paxosPRunner.close()
            self.paxosAcceptor.close()
            self.paxosLearner.close()
        except:
            pass

    def run(self):
        while not self.shouldClose:
            yield waitevent, self, self.closeEvent
            if self.shouldClose:
                for step in self._close():
                    yield step
class Responder(Process):
    
    ANSWER_AT_FIELD = 0
    QUERY_FIELD = 1
    
    def __init__(self, sim, sender=None):
        super(Responder, self).__init__(sim=sim)
        self._random = Random()
        self.__new_query_queued = SimEvent(name="new_query_queued", sim=sim)
        self.local_records = {} # key: record, value: last time advertised
        self.queued_queries = [] # tuple: ( when to answer, query )
        self.sender = sender
    
    def record_changes(self, record):
        for old_record in self.local_records.iterkeys():
            if record == old_record:
                return record.have_data_changed(old_record)
        return False # if that record didn't exist before
    
    def write_record(self, record):
        if record in self.local_records:
            if self.record_changes(record):
                # "Whenever a host has a resource record with new data"                
                # record is equal to a key (__eq__()==True), but has outdated data
                del self.local_records[record] # remove old key with outdated data
                # set new key
                self.local_records[record] = -1 # -1 => next time should be sent using multicast (because is an announcement)
                self.announce(record)
        else:
            # "Whenever a host has a resource record with new data"
            self.local_records[record] = -1
            self.announce(record)
    
    def something_happened(self):
        # Whenever it might potentially be new data (e.g. after rebooting, waking from
        # sleep, connecting to a new network link, changing IP address, etc.)
        for record in self.local_records.iterkeys():
            self.announce(record)
    
    # 10.2 Announcements to Flush Outdated Cache Entries
    def announce(self, announced_record):
        # TODO optimize to announce more than one? is that possible according to the standard?
        
        # Generating a fake query which will never be sent
        sq = SubQuery(announced_record.name, announced_record.type)
        # They may know my other records, I'm just announcing one
        known_answers = [record for record in self.local_records if record!=announced_record]
        q = Query( queries = [sq,], known_answers = known_answers )
        
        # a little trick here, we queue a false query which will result in a response
        # containing the record we want to announce
        self.queue_query(q)
    
    def queue_query(self, query):
        # TODO optimization:
        # if the query is already planned, don't answer twice in such a short period of time
        # if an answer for the same query was answered in the last 1000 ms, wait for the response
        
        if query.response_is_unique():
            # if the response is unique, answer within 10 ms
            when = self.sim.now() + self._random.random() * 10
            self.queued_queries.append( (when, query) )
        else:
            # delay between 20 and 120 ms
            when = self.sim.now() + 20 + self._random.random() * 100
            self.queued_queries.append( (when, query) )
        
        # sorts by the 1st element in the set
        self.queued_queries.sort(key=lambda tup: tup[0])
        
        # wake up wait_for_next_event method
        self.__new_query_queued.signal()
    
    def answer(self):
        while True:
            if not self.queued_queries: # if it's empty...
                yield waitevent, self, (self.__new_query_queued,)
            else:
                next_query = self.queued_queries[0]
                
                if self.sim.now() < next_query[Responder.ANSWER_AT_FIELD]:
                    twait = next_query[Responder.ANSWER_AT_FIELD] - self.sim.now()
                    self.timer = Timer(waitUntil=twait, sim=self.sim)
                    self.timer.event.name = "sleep_until_next_query"
                    self.sim.activate(self.timer, self.timer.wait())
                    yield waitevent, self, (self.timer.event, self.__new_query_queued,)
                else:
                    del self.queued_queries[0] # query will be processed
                    self.process_query( next_query[Responder.QUERY_FIELD] )
    
    def process_query(self, query):
        answers = self._get_possible_answers(query)
        self._suppress_known_answers(query, answers)
        if len(answers)>0: # avoid sending empty UDP messages!
            self._send_using_proper_method(query, answers)
    
    def _get_possible_answers(self, query):
        answers = []
        for subquery in query.queries:
            for record in self.local_records.iterkeys():
                if subquery.record_type == "PTR": # special queries in DNS-SD!
                    
                    if subquery.name == "_services._dns-sd._udp.local":
                        answers.append( deepcopy(record) ) # all of the records
                    elif record.name.endswith(subquery.name):
                        answers.append( deepcopy(record) ) # all of the records
                
                elif subquery.name == record.name and subquery.record_type == record.type:
                    answers.append( deepcopy(record) )
        return answers
    
    def _suppress_known_answers(self, query, answers):
        # Section 7.1.  Known-Answer Suppression
        for known_answer in query.known_answers:
            for record in answers:
                if known_answer.name == record.name and known_answer.type == record.type:
                    answers.remove(record)
    
    def _send_using_proper_method(self, query, answers):
        unicast = query.question_type is "QU" # unicast type
        
        # See 5.4 Questions Requesting Unicast Responses
        if unicast:
            # event if it was marked as unicast, can be sent as multicast
            for record in answers:
                thresold_time = record.ttl * 1000 * 0.25 # ttl is measured in seconds and simulation time in ms!
                last_time_sent = self.local_records[record]
                now = self.sim.now()
                
                if last_time_sent==-1: # never sent before
                    unicast = False
                    self.local_records[record] = now
                elif ( now - last_time_sent ) > thresold_time:
                    unicast = False # not recently advertised, send using multicast
                    self.local_records[record] = now
        
        
        if unicast:
            self.sender.send_unicast( query.to_node, DNSPacket(ttype=DNSPacket.TYPE_RESPONSE, data=answers) )
        else:
            self.sender.send_multicast( DNSPacket(ttype=DNSPacket.TYPE_RESPONSE, data=answers) )
class RequestInstance(Process): # TODO rename to something more meaningful such as RequestSender
    """ This class performs an HTTP request in SimPy """
    
    ReqIdGenerator = 0
    
    def __init__(self, actionNode, destinationNodes, url, data=None, waitUntil=10000.0, name="request", sim=None):
        super(RequestInstance, self).__init__(name=name, sim=sim)
        self.name += " (from=%s, url=%s)"%(actionNode.name, url)
        self.__actionNode = weakref.proxy(actionNode) #weakref.ref(actionNode)
        self.__destinationNodes = weakref.WeakSet(destinationNodes) # tuple with all the nodes to be requested
        self.url = url # accessible
        self.__data = data
        
        self.requestInit = {} # requestInit[reqId1] = now(), requestInit[reqId2] = now()
        self.responses = [] # accessible
        self.__maxWaitingTime = waitUntil
        self.nodeNamesByReqId = {} # used in the gossiping mechanism with the gossiping requests
        
        self.__newResponseReceived = SimEvent(name="request_response_for_%s"%(self.name), sim=sim)
        self.__observers = weakref.WeakSet()
    
        
    def startup(self):
        t_init = self.sim.now()
        
        for node in self.__destinationNodes:
            # already removed from the list prior to calling to this method, but just in case...
            if node is not self.__actionNode:
                reqId = RequestInstance.ReqIdGenerator
                RequestInstance.ReqIdGenerator += 1
                
                request = HttpRequest(reqId, self.url, data=self.__data)
                self.nodeNamesByReqId[reqId] = node.name
                
                self.requestInit[reqId] = self.sim.now()
                node.queueRequest(self, request)
                #if self.__data!=None:
                #    G.executionData.requests['data-exchanged'] += len(self.__data)
            else:
                raise Exception("A request to the same node is impossible! ")
        
        self.timer = Timer(waitUntil=G.timeout_after, sim=self.sim)
        self.timer.event.name = "request_timeout_for_%s"%(self.name)
        self.sim.activate(self.timer, self.timer.wait())#, self.__maxWaitingTime)
        while not self.allReceived() and not self.timer.ended:
            yield waitevent, self, (self.timer.event, self.__newResponseReceived,)
        
        
        if not self.allReceived(): # timeout reached
            #print "Response not received!"
            response_time = self.sim.now() - t_init
            
            for node_name in self.get_unanswered_nodes():
                G.traceRequest(t_init,
                           self.__actionNode.name,
                           node_name,
                           self.url,
                           408, # TIMEOUT. See http://www.restlet.org/documentation/2.0/jse/api/org/restlet/data/Status.html#CLIENT_ERROR_REQUEST_TIMEOUT
                           response_time )
            
            # self.__actionNode.addClientRequestActivityObservation(now()-init, now())
            
            # this information can be extracted from the traces
            # G.executionData.requests['failure'].append(self)
            
        for o in self.__observers:
            o.notifyRequestFinished(self)
            
    def get_unanswered_nodes(self):
        # not yet deleted requestInit keys, are the ids without a response
        return [self.get_destination_node_name(reqId) for reqId in self.requestInit.keys()]
    
    def getWaitingFor(self):
        return len(self.requestInit)
    
    def allReceived(self):
        return self.getWaitingFor()==0
    
    def addResponse(self, response): # TODO associate with a node
        #if response.getstatus()==404:
        #    dest_node_name = self.get_destination_node_name(response.getid())
        #    print dest_node_name
        
        # timeouts have been already taken into account in the 'timeout' counter
        if not self.timer.ended:
            t_init = self.requestInit[response.getid()]
            response_time = self.sim.now() - t_init
            G.traceRequest(t_init,
                           self.__actionNode.name,
                           self.get_destination_node_name(response.getid()),
                           response.geturl(),
                           response.getstatus(),
                           response_time )
            
            #G.executionData.response_time_monitor.observe( now() - t_init ) # request time
            del self.requestInit[response.getid()]
            
            self.responses.append(response) #dest_node_name
            
            #G.executionData.requests['data-exchanged'] += len(response.get_data())
            
            #fileHandle = open ( 'test.txt', 'a' )
            #fileHandle.write ( response.get_data() )
            #fileHandle.close()
            
            self.__newResponseReceived.signal()
            
    def get_destination_node_name(self, responseId):
        return self.nodeNamesByReqId[responseId]
    
    def toString(self):
        for resp in self.responses:
            print resp.getmsg()
    
    def getActionNode(self):
        return self.__actionNode
    
    def addObserver(self, observer):
        self.__observers.add(observer)
class Cache(Process):
    
    WHEN_FIELD = 0
    ACTION_FIELD = 1 # what to do
    RECORD_FIELD = 2
    
    EVENT_KNOWN_ANSWER = "add_to_known_answer_suppression"
    EVENT_RENEW = "try_to_renew"
    EVENT_FLUSH= "flush_record"
    
    def __init__(self, sim, record_observer=None):
        super(Cache, self).__init__(sim=sim)
        self.record_observer = record_observer
        self.__new_record_cached = SimEvent(name="new_record_cached", sim=sim)
        self._random = Random()
        self.pending_events = [] # tuples with the form (when, action, record)
        self.records = [] # cached records
    
    def get_known_answers(self):
        known_answers = []
        for record in self.records:
            found = False
            for event in self.pending_events:
                if event[Cache.ACTION_FIELD] == Cache.EVENT_KNOWN_ANSWER and event[Cache.RECORD_FIELD] == record:
                    found = True
                    break
            if found:
                known_answers.append( deepcopy(record) )
        return known_answers
    
    def _delete_events_for_record(self, record):
        to_delete = []
        for event in self.pending_events:
            if event[Cache.RECORD_FIELD] == record:
                to_delete.append(event)
        
        for event in to_delete:
            self.pending_events.remove(event)
    
    def _get_time_after_percentage(self, ttl, percentage):
        """Percentage example: 0.45 (means 45%)"""
        # remember that ttl is measured in seconds and simulation time in ms!
        return self.sim.now() + ttl * 1000 * percentage 
    
    def _create_new_events(self, record):
        ttl = record.ttl
        
        # at 1/2 of the TTL => does not add to known answer suppression
        when = self._get_time_after_percentage(ttl, 0.5)
        self.pending_events.append( (when, Cache.EVENT_KNOWN_ANSWER, record) )
        
        # section 5.2, http://tools.ietf.org/html/draft-cheshire-dnsext-multicastdns-15
        # at 80%, 85%, 90% and 95% of the TTL => try to renew the record
        for percentage in ( 80, 85, 90, 95 ):
            percentage_with_variation = (percentage + self._random.random()*2) / 100.0 # 2% of variation
            when = self._get_time_after_percentage(ttl, percentage_with_variation)
            self.pending_events.append( (when, Cache.EVENT_RENEW, record) )
    
    def cache_record(self, record):
        self._delete_events_for_record(record)
        self._create_new_events(record)
        
        if record in self.records:
            self.records.remove(record) # does delete the previous one?
        self.records.append(record)
        
        # sorts by the 1st element in the set
        self.pending_events.sort(key=lambda tup: tup[0])
        
        # wake up wait_for_next_event method
        self.__new_record_cached.signal()
    
    def flush_all(self):
        del self.records[:]
        del self.pending_events[:]
    
    # Inspired by RequestInstance class
    def wait_for_next_event(self):
        while True:
            
            if not self.pending_events: # if it's empty...
                yield waitevent, self, (self.__new_record_cached,)
            else:
                next_event = self.pending_events[0]
                
                if self.sim.now() < next_event[Cache.WHEN_FIELD]:
                    twait = next_event[Cache.WHEN_FIELD]-self.sim.now()
                    self.timer = Timer(waitUntil=twait, sim=self.sim)
                    self.timer.event.name = "sleep_until_next_event"
                    self.sim.activate(self.timer, self.timer.wait())
                    yield waitevent, self, (self.timer.event, self.__new_record_cached,)
                else:
                    del self.pending_events[0] # action will be taken
                    
                    if next_event[Cache.ACTION_FIELD] == Cache.EVENT_FLUSH:
                        # delete old record
                        self.records.remove( next_event[Cache.RECORD_FIELD] )
                        
                    elif next_event[Cache.ACTION_FIELD] == Cache.EVENT_RENEW:
                        if self.record_observer is not None:
                            self.record_observer.renew_record( next_event[Cache.RECORD_FIELD] )
示例#38
0
文件: dsr.py 项目: reidlindsay/wins
class DSR(Routing):
    """Dynamic Source Routing protocol (RFC 4728).

    This implementation uses link-level acknowledgements to do route
    maintenance (see `MAC.drpdata` and `MAC.ackdata`).

    :ivar rreqtable: `RouteRequestTable` used to manage route requests.
    :ivar rreqrate: Rate annotation applied to all Route Requests.
    :ivar datarate: Rate annotation applied to all unicast messages.
    :ivar maintbuffer: Dictionary containing buffer of packets being maintained
                       by DSR, indexed by next hop addresses.

    :cvar mac: Property to access pointer to `MAC`.
    :cvar rrt: Alias for `rreqtable`.
    :cvar MaxMaintRexmt: Maximum number of retransmission for route maintenance.
    """
    name = "DSR"
    tracename = "DSR"
    MaxMaintRexmt = 0
    DiscoveryHopLimit = 255
    MaxTTL = DiscoveryHopLimit
    BroadcastJitter = 10e-3
    MaintenanceTimeout = 10.0
    RexmtBufferSize = 50
    MaxGratuitousRexmt = 6
    def __init__(self, *args, **kwargs):
        """Constructor."""
        # set up parameters
        self.__mac = None
        self.rreqrate = None
        self.datarate = None
        self.maintbuffer = None
        # additional parameters for signalling
        self.sndrreq = SimEvent()
        self.drprreq = SimEvent()
        self.finrreq = SimEvent()
        self.sndfail = SimEvent()
        # call base constructor
        Routing.__init__(self, *args, **kwargs)

    mac = property(fget=lambda self: self.get_mac(), \
                   fset=lambda self, m: self.set_mac(m) )
    rrt = property(fget=lambda self: self.rreqtable)
    promiscuous = property(fget=lambda self: self.get_promiscuous() )

    def configure(self, mac=None, rreqrate=None, datarate=None, **kwargs):
        """Configure pointers and parameters.

        :param mac: `MAC` module corresponding to this network protocol.
        :param rreqrate: Rate index used for flooding RREQ messages.
        :param datarate: Rate index used for sending other DSR messages.
        :param kwargs: Additional keywords passed to `Routing.configure()`.

        If `mac` is not provided, this module will attempt to automatically
        determine the appropriate `MAC`.

        If `rreqrate` and/or `datarate` are not specified, `DSR` will not
        attempt to set the rate annotation of the outgoing packet. It is
        important that the `rreqrate` be higher than `datarate` to ensure stable
        links for DSR.
        """
        Routing.configure(self, **kwargs)
        self.mac = mac
        self.rreqrate = rreqrate
        self.datarate = datarate
        self.maintbuffer = {}
        self.table = self.newchild('routingtable', RouteCache, \
                                   name=self.name+".rcache", \
                                   tracename=self.tracename+".RCACHE")
        rreqtable  = self.newchild('rreqtable', RouteRequestTable, \
                                   name=self.name+".rreqtable", \
                                   tracename=self.tracename+".RREQTBL")

    ##############################
    # TX STATES
    ##############################
    def IPFORWARD(self, fsm, p):
        """IPFORWARD state; start route maintenance for IP+DSR if needed.

        :param p: IP packet to send.

        :note: This state assumes a valid IP+DSR packet was went to it.
        """
        # get IP parameters
        ip, dsr = p[IP], p[DSRPacket]
        src, dst = ip.src, ip.dst
        # start route maintenance or deliver
        self.checkdsropt(ip, exception=True, incoming=False)
        # send broadcast immediately
        if (dst==self.broadcast):
            yield fsm.goto(self.IPDELIVER, ip, dst)
        # otherwise ...
        srcroute = getdsr_srcroute(ip)
        if srcroute:
            path = srcroute.addresses
            segsleft, naddr = srcroute.segsleft, len(path)
            nexthop = path[-segsleft]
        else:
            nexthop = dst
        # cache route to nexthop and start route maintenance for ip
        self.addroute(nexthop)
        self.maintain(ip, nexthop)      # put in maintbuffer

    def IPROUTING(self, fsm, p):
        """IPROUTING state; start route discovery for `p`."""
        # error messages
        dropbuffer = "sendbuffer overflow"
        # get IP/DSR/Option parameters
        ip, dsr = p[IP], p[DSRPacket]
        src, dst = ip.src, ip.dst
        # has route -> resend
        if self.hasroute(dst):
            yield fsm.goto(self.IPSEND, ip, src, dst)
        # otherwise -> start route discovery
        rre = self.rrt.getentry(dst)
        drop = rre.buffer(p)
        kwargs = {'src':src, 'dst':dst}
        kwargs['nbuffered'] = len(rre.sendbuffer)
        kwargs['timeleft']  = time2msec(rre.timeleft())
        self.debug("SENDBUFF", p, **kwargs)
        for d in drop:
            self.log_drop(d, drop=dropbuffer)
        # send RREQ
        yield fsm.goto(self.TXRREQ, dst)

    ##############################
    # TX DSR OPTIONS
    ##############################
    def TXRREQ(self, fsm, target, options=[], rexmt=0):
        """TXRREQ state; create and send route request.

        :param target: Target for route discovery.
        :param options: Additional DSR options [default=None].

        :note: This state is persistent. It will keep trying to send until
        """
        # error messages
        rreqerror = "[DSR]: Error getting RREQ Table entry!"
        droprexmt = "max rexmt exceeded"
        drophasrt = "route already exists"
        # pause for jitter
        jitter = random.uniform(0,1)*self.BroadcastJitter
        yield hold, fsm, jitter
        # check route and rexmt count
        if self.hasroute(target):
            self.log("RREQSTOP", target=target, rexmt=rexmt, drop=drophasrt)
            rre = self.rrt.getentry(target)
            while rre.sendbuffer:
                p = rre.sendbuffer.pop(0)
                f = FSM.launch(self.IPRECOVER, p, self.address, target)
            self.rrt.delentry(target)
            # signal that RREQ has finished
            self.finrreq.signal(target)
            yield fsm.stop()    # HALT and stop sending RREQ
        if (rexmt>self.rrt.MaxRequestRexmt):
            self.log("RREQDROP", target=target, rexmt=rexmt, drop=droprexmt)
            rre = self.rrt.getentry(target)
            while rre.sendbuffer:
                p = rre.sendbuffer.pop(0)
                self.log_drop(p, drop=droprexmt)
            self.rrt.delentry(target)
            # signal that RREQ has been abandoned
            self.drprreq.signal(target)
            yield fsm.stop()    # HALT and stop sending RREQ
        # get RREQ parameters
        sendrreq = self.rrt.sendrreq(target)
        rre = self.rrt.getentry(target)
        tleft = rre.timeleft()
        # get parameters for logging
        kwargs = {'rexmt':rexmt, 'nbuffered': len(rre.sendbuffer)}
        kwargs['options']  = [o.tracename for o in options]
        kwargs['jitter'] = time2msec(jitter)
        kwargs['timeleft'] = time2msec(tleft)
        # cannot send RREQ? -> RREQ is busy, drop attempt
        if not sendrreq:
            self.debug("RREQBUSY", target=target, **kwargs)
            yield fsm.stop()    # HALT and allow other RREQ to finish
        # otherwise -> send RREQ
        ID, ttl = rre.ID, rre.ttl
        # create DSR+RREQ+options
        nextheader = self.getproto(None)
        dsr = DSRPacket(nextheader=nextheader)
        rreq = DSROPT_RREQ(identification=ID, target=target)
        dsr.options = [rreq] + [o for o in options]
        # create IP+DSR
        proto = self.getproto(dsr)
        src, dst = self.address, self.broadcast
        ip = IP(src=src, dst=dst, proto=proto, ttl=ttl)
        ip.add_payload(dsr)
        # send RREQ -> wait for timeout, then rexmt
        self.debug("TXRREQ", ip, target=target, **kwargs)
        f = FSM.launch(self.IPDELIVER, ip, dst)
        # signal that RREQ has been sent to target
        self.sndrreq.signal(target)
        # wait for send RREQ to timeout before trying again
        yield hold, fsm, tleft
        yield fsm.goto(self.TXRREQ, target, options, rexmt+1)

    def TXRREP(self, fsm, src, dst, rreq, addresses):
        """TXRREP state; create and send route reply to `dst`."""
        self.debug("TXRREP", src=src, dst=dst, addresses=addresses)
        # cache reverse route from rreq?
        opt = getdsr_rreq(rreq)
        if opt and DSR_USE_REVERSE_ROUTES:
            rpath = [a for a in opt.addresses]
            rpath.reverse()
            if rpath: self.addroute(dst, rpath)     # more than one hop away
            else:     self.addroute(dst)            # one hop neighbor
        # create RREP option
        rrep = DSROPT_RREP()
        rrep.addresses = [a for a in addresses]
        # no route to dst -> send RREQ+RREP
        if not self.hasroute(dst):
            yield fsm.goto(self.TXRREQ, dst, options=[rrep])
        # otherwise -> ...
        if opt and DSR_USE_REVERSE_ROUTES:
            path = [a for a in rpath]
        else:
            path = self.srcroute(dst)
        ttl = len(path)+1
        # create DSR
        nextheader = self.getproto(None)
        dsr = DSRPacket(nextheader=nextheader)
        dsr.options = [rrep]
        # create IP
        proto = self.getproto(dsr)
        ip = IP(src=src, dst=dst, proto=proto, ttl=ttl)
        ip.add_payload(dsr)
        # add SRCROUTE?
        pkt = self.updateroute(ip, path)
        #assert (pkt is not None)
        assert self.safe((pkt is not None))
        yield fsm.goto(self.IPFORWARD, pkt)

    def TXRERR(self, fsm, src, dst, errortype, type_specific):
        """TXRERR state; create and send a new route error message."""
        self.debug("TXRERR", src=src, dst=dst, unreachable=type_specific)
        # erro messages
        errornotme = "[DSR]: Cannot send RERR not from me!"
        assert self.safe(src==self.address), errornotme
        # create RERR option
        rerr = DSROPT_RERR()
        rerr.err_src, rerr.err_dst = src, dst
        rerr.errortype, rerr.type_specific = errortype, type_specific
        # create DSR + RERR Option
        nextheader = self.getproto(None)
        dsr = DSRPacket(nextheader=nextheader)
        dsr.options = [rerr]
        # create IP + DSR
        proto = self.getproto(dsr)
        ip = IP(src=src, dst=dst, proto=proto)
        ip.add_payload(dsr)
        # no route -> start route discovery?
        if not self.hasroute(dst):
            yield fsm.goto(self.IPSEND, ip, src, dst)
        # otherwise -> ...
        path = self.srcroute(dst)
        ttl = len(path)+1
        # add SRCROUTE?
        ip.ttl = ttl
        pkt = self.updateroute(ip, path)
        #assert (pkt is not None)
        assert self.safe((pkt is not None))
        yield fsm.goto(self.IPFORWARD, pkt)

    def MAINT(self, fsm, nexthop, rexmt=0, send=True):
        """MAINT state; perform route maintenace for nexthop.

        :note: Assume maintenance buffer contains valid IP+DSR packets.
        """
        yield hold, fsm, 0  # yield to other threads
        #assert (nexthop in self.maintbuffer)
        assert self.safe((nexthop in self.maintbuffer))
        # error messages
        droproute = "broken route"
        # get maintenance parameters
        buff = self.maintbuffer[nexthop]['buffer']
        if (len(buff)<1):
            del self.maintbuffer[nexthop]
            yield fsm.stop()        # HALT and stop maintenance
        # get head of buffer
        p = buff[0]
        ip, dsr = p[IP], p[DSRPacket]
        addr, src, dst = self.address, ip.src, ip.dst
        # check if path is still valid
        opt, path = getdsr_srcroute(ip), None   # one hop away?
        if opt:
            segsleft = opt.segsleft             # more than one hop?
            path = [a for a in opt.addresses[-segsleft:]]
        pathok = self.hasroute(dst, path)
        # if path is broken -> drop or resend
        if not pathok:
            p = buff.pop(0)
            if (addr==src):
                f = FSM.launch(self.IPRECOVER, p, src, dst)
            else:
                self.log_drop(p, drop=droproute)
            yield fsm.goto(self.MAINT, nexthop)     # continue with next packet
        # otherwise -> send head of buffer?
        if send:
            f = FSM.launch(self.IPDELIVER, ip, nexthop)
        # wait for link-level feedback (ACK/DROP)
        mac, feedback = self.mac, None
        yield waitevent, fsm, (mac.ackdata, mac.drpdata)
        if (mac.ackdata in fsm.eventsFired):
            p = mac.ackdata.signalparam
            if self.issame(ip, p): feedback = "ackdata"
        elif (mac.drpdata in fsm.eventsFired):
            p = mac.drpdata.signalparam
            if self.issame(ip, p): feedback = "drpdata"
        # process feedback
        if (feedback=="ackdata"):
            p = buff.pop(0)
            yield fsm.goto(self.MAINT, nexthop)     # continue with next packet
        elif (feedback=="drpdata"):
            rexmt += 1
            norexmt = (rexmt>self.MaxMaintRexmt)
            # rexmt not exceeded -> try again
            if not norexmt:
                self.debug("REXMT%d"%(rexmt), ip)
                yield fsm.goto(self.MAINT, nexthop, rexmt)
            # otherwise -> broken link!!
            etype = DSR_ERROR_NODE_UNREACHABLE
            esrc, unreachable = self.address, nexthop
            self.debug("DROPDATA", src=esrc, unreachable=unreachable)
            self.removelink(esrc, unreachable)
            # signal broken link
            self.sndfail.signal(esrc)
            # clear out maintenance buffer
            errdst = []
            while buff:
                p = buff.pop(0)
                ip, dsr = p[IP], p[DSRPacket]
                src, dst = ip.src, ip.dst
                # send RERR (for non-RREP messages)
                rrep = getdsr_rrep(ip)
                sendrerr = (not rrep) and (src not in errdst)
                # recover packet or send RERR
                if (addr==src):
                    f = FSM.launch(self.IPRECOVER, p, src, dst)
                elif sendrerr:
                    errdst.append(src)
            # send RERR to sources
            for edst in errdst:
                f = FSM.launch(self.TXRERR, esrc, edst, etype, unreachable)
            # continue to allow graceful shutdown
            yield fsm.goto(self.MAINT, nexthop)
        else:
            # feedback not for me -> continue waiting
            yield fsm.goto(self.MAINT, nexthop, rexmt, send=False)

    def IPRECOVER(self, fsm, p, src, dst):
        """IPRECOVER state; attempt to resend packet `p`.

        :note: This method assumes that `p` is a valid IP+DSR packet.
        """
        self.debug("IPRECOVER", p, src=src, dst=dst)
        # error messages
        errornotme = "[DSR]: Cannot recover message not from me!"
        errorbcast = "[DSR]: Cannot recover broadcast messages!"
        droprcount = "recover attempts exceeded"
        assert self.safe(src==self.address), errornotme
        assert self.safe(dst!=self.broadcast), errorbcast
        # drop RREP or RERR packets
        rrep = getdsr_rrep(p)
        rerr = getdsr_rerr(p)
        drop = ""
        if rrep: drop = "do not recover RREP"
        if rerr: drop = "do not recover RERR"
        if drop:
            self.log_drop(p, src=src, dst=dst, drop=drop)
            yield fsm.stop()            # HALT and discard packet
        # keep secret annotation for keeping track of recovery attempts
        rcount = 0
        if p.hasanno('iprecover'): rcount = p.getanno('iprecover') + 1
        p.setanno('iprecover', rcount, priv=True)
        # max retries exceeded?
        if (rcount>1): # FIXME: use (>self.MaxMaintRexmt) instead?
            self.log_drop(p, src=src, dst=dst, rcount=rcount, drop=droprcount)
            yield fsm.stop()
        # otherwise -> remove stale route, update, and resend
        ip, dsr = p[IP], p[DSRPacket]
        dsr.options = [o for o in dsr.options if (not getdsr_srcroute(o))]
        assert (dsr.nextheader!=const.IP_PROTO_NONE)
        yield fsm.goto(self.IPSEND, ip, src, dst)

    ##############################
    # RX STATES
    ##############################
    def IPCLASSIFY(self, fsm, p, cache=True):
        """IPCLASSIFY state; overloaded to implement DSR classification.

        :param p: Received IP packet.
        :param cache: Cache routing information from `p`.

        :note: This state assumes that packet `p` passed all validity checks in
               `IPRECV` (i.e. `checkiprecv()`).
        """
        errmsg = "[DSR]: IPCLASSIFY does not support promiscuous mode."
        #assert (not self.promiscuous), errmsg
        assert self.safe((not self.promiscuous)), errmsg
        # error messages
        errordsropt  = "[DSR]: Unprocessed DSR options still remain!"
        dropnotforme = "not for me"
        # get IP+DSR packets
        ip = p[IP]
        dsr = ip[DSRPacket]
        # get IP/DSR parameters
        addr, bcast = self.address, self.broadcast
        src, dst = ip.src, ip.dst
        # get DSR options
        rreq, rrep     = getdsr_rreq(ip), getdsr_rrep(ip)
        rerr, srcroute = getdsr_rerr(ip), getdsr_srcroute(ip)
        isforme, isbcast = (dst==addr), (dst==bcast)
        unicast, promiscuous = (not isbcast), self.promiscuous
        # cache routing info from packet
        if cache: self.cacheroute(ip)
        # classify DSR options
        if srcroute and unicast:
            yield fsm.goto(self.RXSRCROUTE, ip, dsr, srcroute)
        elif rreq and isbcast:
            yield fsm.goto(self.RXRREQ,     ip, dsr, rreq)
        elif rrep and isforme:
            yield fsm.goto(self.RXRREP,     ip, dsr, rrep)
        elif rerr and isforme:
            yield fsm.goto(self.RXRERR,     ip, dsr, rerr)
        elif dsr.options:
            #assert (self.promiscuous or (not isforme)), errordsropt
            assert self.safe((self.promiscuous or (not isforme))), errordsropt
        # classify payload of packet
        if (dst==addr) or (dst==bcast):
            # packet for me -> detach payload
            payload = dsr.payload
            dsr.remove_payload()
            self.log_recv(payload, src=src, dst=dst)
            if (dsr.nextheader==const.IP_PROTO_IP):
                yield fsm.goto(self.IPRECV, payload)    # IPENCAP -> reclassify
            elif (dsr.nextheader!=const.IP_PROTO_NONE):
                pkt = self.set_recvanno(payload, src, dst)
                yield self.TXU.send(fsm, [pkt])         # DATA -> send to upper layer
        else:
            # otherwise -> drop
            self.log_drop(ip, drop=dropnotforme)

    ##############################
    # RX DSR OPTIONS
    ##############################
    def RXSRCROUTE(self, fsm, ip, dsr, opt):
        """RXSRCROUTE state; process source route option.

        :note: Assumes `checkiprecv()` passed.
        """
        self.debug("RXSRCROUTE", ip)
        # get IP/DSR/Option parameters
        segsleft = opt.segsleft
        path = opt.addresses[-segsleft]
        # forward packet to next hop in source route
        if (segsleft>1):
            # intermediate hop -> update SRCROUTE and send
            ip.ttl = ip.ttl - 1
            opt.segsleft = segsleft - 1
            yield fsm.goto(self.IPFORWARD, ip)
        elif (segsleft==1):
            # last hop -> strip SRCROUTE and send
            ip.ttl = ip.ttl - 1
            dsr.options = [o for o in dsr.options if (not getdsr_srcroute(o))]
            yield fsm.goto(self.IPFORWARD, ip)

    def RXRREQ(self, fsm, ip, dsr, opt):
        """RXRREQ state; process route request option.

        :note: Assumes `checkiprecv()` passed.
        """
        self.debug("RXRREQ", ip)
        # error messages
        droprreq = "in source route or not new RREQ"
        # get IP/DSR/Option parameters
        addr, src, dst = self.address, ip.src, ip.dst
        ID, target = opt.identification, opt.target
        rreqonly = (dsr.nextheader==const.IP_PROTO_NONE)
        # check if target reached
        if (addr==target):
            # send RREP [and process remaining packet]
            path = [a for a in opt.addresses] + [target]
            f = FSM.launch(self.TXRREP, addr, src, opt, path)
            # strip RREQ and set dst=target
            dsr.options = [o for o in dsr.options if (not getdsr_rreq(o))]
            ip.dst = target
            # check for other options
            rerr, rrep = getdsr_rerr(dsr), getdsr_rrep(dsr)
            if rerr:
                yield fsm.goto(self.RXRERR, ip, dsr, rerr)
            elif rrep:
                yield fsm.goto(self.RXRREP, ip, dsr, rrep)
            # RREQ only -> HALT and discard packet
            if rreqonly: yield fsm.stop()
            # otherwise -> remove DSR options and reclassify
            dsr.options = []
            yield fsm.goto(self.IRECV, ip)
        # otherwise -> attempt to forward RREQ
        assert (addr!=target)
        inroute = (src==addr) or (addr in opt.addresses)
        newrreq = self.rrt.addcache(src, ID, target)
        # check if RREQ should be dropped
        if (inroute or (not newrreq)):
            self.log_drop(ip, drop=droprreq)
            yield fsm.stop()    # HALT and discard packet
        # update options and forward RREQ
        ip.ttl = ip.ttl - 1
        opt.addresses = [a for a in opt.addresses] + [addr]
        yield fsm.goto(self.IPFORWARD, ip)

    def RXRREP(self, fsm, ip, dsr, opt):
        """RXRREP state; handle route reply message.

        :note: Assumes `checkiprecv()` passed.
        """
        self.debug("RXRREP", ip)
        # get IP/DSR/Option parameters
        src, dst = ip.src, ip.dst
        addr, target = self.address, opt.addresses[-1]
        rreponly = (dsr.nextheader==const.IP_PROTO_NONE)
        # only keep RREP options and update route cache
        assert (dst==addr)
        dsr.options = [o for o in dsr.options if (getdsr_rrep(o))]
        self.cacheroute(ip)
        #assert (self.hasroute(target))
        assert self.safe((self.hasroute(target)))   # verify route to target
        # get entry from Route Request Table and forward packets
        rre = self.rrt.getentry(target)
        while rre.sendbuffer:
            p = rre.sendbuffer.pop(0)
            f = FSM.launch(self.IPRECOVER, p, addr, target)
        self.rrt.delentry(target)
        # more than RREP?
        if not rreponly:
            # remove DSR options and reclassify
            dsr.options = []
            yield fsm.goto(self.IRECV, ip)

    def RXRERR(self, fsm, ip, dsr, opt):
        """RXRERR state; handle route error message.

        :note: Assumes `checkiprecv()` passed.
        """
        self.debug("RXRRER", ip)
        # error messages
        droptype = "unsupported route error type"
        # get IP/DSR/Option parameters
        addr, src, dst = self.address, ip.src, ip.dst
        errsrc, errdst = opt.err_src, opt.err_dst
        errortype, unreachable = opt.errortype, opt.type_specific
        rerronly = (dsr.nextheader==const.IP_PROTO_NONE)
        #assert (addr==dst)
        assert self.safe((addr==dst))
        # process RERR packet (or drop unsupported errortype)
        if (errortype==DSR_ERROR_NODE_UNREACHABLE):
            self.removelink(errsrc, unreachable)
        else:
            self.log_drop(ip, drop=droptype)
        # more than RERR?
        if not rerronly:
            # remove DSR options and reclassify
            dsr.options = []
            yield fsm.goto(self.IRECV, ip)

    ##############################
    # HELPER METHODS
    ##############################
    def encap_data(self, p, src, dst, force=False, **kwargs):
        """Encapsulate data packet (if needed) for delivery to next hop.

        :param p: Packet to deliver.
        :param src: Source address.
        :param dst: Destination address.
        :param force: If true, force encapsulation into a new IP packet.
        :param kwargs: Additional keywords passed to IP constructor.
        :return: Encapsulated/modified packet for delivery.

        If `p` is already an IP packet and `force` is false, this method will
        update 'src', 'dst', and 'ttl' fields as needed. Otherwise, this method
        encapsulates `p` with an IP header.

        :note: By default this method assumes that `ptype` indicates IPv4.
               *Overload this method to handle other protocol types*.
        """
        # if no encapsulation needed -> update parameters
        isip = isinstance(p, Packet) and p.haslayer(IP)
        isdsr = isip and p.haslayer(DSRPacket)
        if isdsr and (not force):
            ip, dsr = p[IP], p[DSRPacket]
            dsr.nextheader = self.getproto(dsr.payload)
            if self.hasroute(ip.dst):
                p = self.updateroute(ip)
            # update IP parameters
            return Routing.encap_data(self, p, src, dst, force=False, **kwargs)
        # create DSR
        nextheader = self.getproto(p)
        dsr = DSRPacket(nextheader=nextheader)
        dsr.add_payload(p)
        # create IP+DSR
        proto = self.getproto(dsr)
        ip = IP(src=src, dst=dst, proto=proto, **kwargs)
        ip.add_payload(dsr)
        # update and return
        return self.encap_data(ip, src, dst, force=False, **kwargs)

    def maintain(self, p, nexthop):
        """Start or udpate route maintenace on IP+DSR packet.

        :note: This assumes a IP+DSR packet was passed to it.
        """
        # error messages
        errornexthop = "[DSR]: maintain() found invalid nexthop!"
        dropbuffer = "maintenance buffer overflow"
        dropttl = "TTL expired"
        # get IP/DSR/Option parameters
        ip, dsr = p[IP], p[DSRPacket]
        addr, src, dst, ttl = self.address, ip.src, ip.dst, ip.ttl
        # TTL expired?
        if (ttl<1):
            self.log_drop(p, drop=dropttl)
            return
        # start or continue route maintenance
        if nexthop in self.maintbuffer:
            # add packet to end of maintenance buffer
            buff = self.maintbuffer[nexthop]['buffer']
            if (len(buff)<self.RexmtBufferSize):
                buff.append(ip)
                self.debug("MAINTBUFF", ip)
            else:
                self.log_drop(ip, drop=dropbuffer)
        else:
            # add new buffer and launch thread to maintain it
            self.maintbuffer[nexthop] = {'buffer':[ip]}
            f = FSM.launch(self.MAINT, nexthop)

    def cacheroute(self, p):
        """Cache relevant route information from IP+DSR packet.

        :note: This method assumes that the packet has passed through
               `checkiprecv()`.
        """
        errmsg = "[DSR]: cacheroute() does not support promiscuous mode."
        #assert (not self.promiscuous), errmsg
        assert self.safe((not self.promiscuous)), errmsg
        # get ip/dsr packets and parameters
        ip = p[IP]
        dsr = p[DSRPacket]
        addr, bcast = self.address, self.broadcast
        src, dst, ttl = ip.src, ip.dst, ip.ttl
        # classify based on DSR option
        rreq, rrep     = getdsr_rreq(ip), getdsr_rrep(ip)
        rerr, srcroute = getdsr_rerr(ip), getdsr_srcroute(ip)
        # process source route option
        if srcroute:
            # cache remaining route to dst
            segsleft = srcroute.segsleft
            path = srcroute.addresses[-segsleft:]
            if (segsleft>1):     self.addroute(dst, path[1:])
            elif (segsleft==1): self.addroute(dst)
        # process route request option
        if rreq:
            # no routing info to cache, but use target as dst for other options
            dst = rreq.target
        # process route reply option
        if rrep:
            # check if on route or intended dst of RREP
            inroute = (addr in rrep.addresses[:-1])
            isforme = (dst==addr)
            # cache routing info
            idx = None
            if inroute:   idx = rrep.addresses.index(addr)+1
            elif isforme: idx = 0
            if (idx is not None):
                target = rrep.addresses[-1]
                rt = rrep.addresses[idx:-1]
                if rt: self.addroute(target, rt)    # more than one hop away
                else:  self.addroute(target)        # target is neighbor
        # process route error option
        if rerr:
            errsrc, unreachable = rerr.err_src, rerr.type_specific
            if (rerr.errortype==DSR_ERROR_NODE_UNREACHABLE):
                self.removelink(errsrc, unreachable)

    def checkiprecv(self, p, nextheader=True, **kwargs):
        """Overloaded to check for valid IP+DSR packet.

        :param p: Packet received in `IPRECV`.
        :param args: Additional arguments passed to base class method.
        :param kwargs: Additional keywords passed to base class method.
        :return: String containing any error condition, or nothing for no error.
        """
        # error messages
        dropnondsr = "non-IP+DSR packet in IPRECV"
        dropheader = "invalid IP+DSR nextheader"
        dropdsropt = "invalid DSR options!"
        # call base method
        drop = Routing.checkiprecv(self, p, **kwargs)
        if drop: return drop
        # valid IP+DSR?
        isdsr = p.haslayer(IP) and p.haslayer(DSRPacket)
        if not isdsr: return dropnondsr
        dsr = p[DSRPacket]
        # check nextheader
        if nextheader:
            payproto = self.getproto(dsr.payload)
            if (payproto!=dsr.nextheader): return dropheader
        # check DSR options
        if not self.checkdsropt(p, exception=False):
            return dropdsropt
        return ""   # no error

    def checkdsropt(self, p, exception=True, incoming=True):
        """Check DSR options of incoming packet for any violations.

        :param p: Packet to check.
        :param exception: If true, throws exception for violation.
        :param incoming: If true, treat `p` as a received packet.
        :return: Boolean flag; if True, DSR packet is okay.

        :note: Do not use this method to check options for outgoing packets
               without setting `incoming` to False.
        """
        if isinstance(p, Reference): p = p._deref
        isip = isinstance(p, Packet) and p.haslayer(IP)
        isdsr = isip and p.haslayer(DSRPacket)
        if not isdsr:
            if exception: raise RuntimeError, "[DSR]: non-IP+DSR packet!"
            return False
        # get IP+DSR packets
        ip  = p[IP]
        dsr = ip[DSRPacket]
        # get IP/DSR parameters
        addr, bcast = self.address, self.broadcast
        src, dst = ip.src, ip.dst
        # classify DSR options
        rreq, rrep     = getdsr_rreq(ip), getdsr_rrep(ip)
        rerr, srcroute = getdsr_rerr(ip), getdsr_srcroute(ip)
        isforme, isbcast = (dst==addr), (dst==bcast)
        unicast, promiscuous = (not isbcast), self.promiscuous
        # error check options
        dsrok = True
        if srcroute:
            path = srcroute.addresses
            segsleft, naddr = srcroute.segsleft, len(path)
            dsrok &= not rreq
            dsrok &= not isbcast
            dsrok &= (segsleft>0) and (segsleft<naddr+1)
            dsrok &= not (isforme and (not promiscuous))
            if dsrok and (not promiscuous):
                if incoming:
                    # incoming -> I am current hop?
                    currhop = path[-segsleft]
                    dsrok &= (currhop==addr)
                else:
                    # outgoing -> I am right before nexthop?
                    currhop = path[-segsleft]
                    if (currhop==path[0]):
                        dsrok &= segsleft==naddr
                        dsrok &= (addr==src)
                    else:
                        dsrok &= (segsleft<naddr)
                        if dsrok: dsrok &= (addr==path[-(segsleft+1)])
            #if exception: assert dsrok, "[DSR]: Invalid SRCROUTE option!"
            if exception: assert self.safe(dsrok), "[DSR]: Invalid SRCROUTE option!"
        if rreq:
            dsrok &= not srcroute
            dsrok &= not unicast
            #if exception: assert dsrok, "[DSR]: Invalid RREQ option!"
            if exception: assert self.safe(dsrok), "[DSR]: Invalid RREQ option!"
        if rrep:
            dsrok &= not (isbcast and (not rreq))
            dsrok &= not rerr
            dsrok &= (len(rrep.addresses)>0)
            #if exception: assert dsrok, "[DSR]: Invalid RREP option!"
            if exception: assert self.safe(dsrok), "[DSR]: Invalid RREP option!"
        if rerr:
            dsrok &= not (isbcast and (not rreq))
            dsrok &= not rrep
            dsrok &= (rerr.errortype==DSR_ERROR_NODE_UNREACHABLE)
            #if exception: assert dsrok, "[DSR]: Invalid RERR option!"
            if exception: assert self.safe(dsrok), "[DSR]: Invalid RERR option!"
        return dsrok

    def updateroute(self, p, path=None, chksum=True):
        """Update IP+DSR routing info using a particular route.

        :param p: IP+DSR packet.
        :param path: New route; if `None`, get it from route cache.
        :param chksum: If true, update checksum.
        :return: Packet with updated routing info (or `None` if no valid route).
        """
        ip, dsr = p[IP], p[DSRPacket]
        # get IP/DSR/Option parameters
        addr, src, dst = self.address, ip.src, ip.dst
        if path is None: path = self.srcroute(dst)
        # no path found?
        if path is None: return None
        # strip old SRCROUTE
        dsr.options = [o for o in dsr.options if (not getdsr_srcroute(o))]
        # apply new route
        ip.ttl = len(path) + 1
        if path:
            srcroute = DSROPT_SRCROUTE()
            srcroute.segsleft = len(path)
            srcroute.addresses = [a for a in path]
            dsr.options = [srcroute] + [o for o in dsr.options]
        # update chksum?
        if chksum: chksum = self.updatechksum(ip, overwrite=True)
        return ip

    def set_sendanno(self, *args, **kwargs):
        """Overloaded to set rate annotations in outgoing packets."""
        p = Routing.set_sendanno(self, *args, **kwargs)
        isip = isinstance(p, Packet) and p.haslayer(IP)
        isdsr = isip and p.haslayer(DSRPacket)
        if not isdsr: return p
        # remove stale annotations
        if p.hasanno('phy-fixed-rate'): p.delanno('phy-fixed-rate')
        # set rate annotation
        rreq = getdsr_rreq(p)
        rate = None
        if rreq: rate = self.rreqrate   # assume RREQ rate is higher
        else:    rate = self.datarate
        if (rate is not None):
            p.setanno('phy-rate', rate)
            # use fixed-rate anno for broadcast packets
            if rreq: p.setanno('phy-fixed-rate', rate)
        return p

    def getproto(self, p):
        """Overloaded to check for DSR packets."""
        if isinstance(p, Reference): p = p._deref
        # determine protocol type of packet
        if isinstance(p, DSRPacket): proto = const.IP_PROTO_DSR
        else:                        proto = Routing.getproto(self, p)
        return proto

    def issame(self, pa, pb):
        """See if packets have the same IP+DSR header information."""
        isip  = isinstance(pa,Packet) and pa.haslayer(IP)
        isdsr = isip and pa.haslayer(DSRPacket)
        if not isdsr: return False
        isip  = isinstance(pb,Packet) and pb.haslayer(IP)
        isdsr = isip and pb.haslayer(DSRPacket)
        if not isdsr: return False
        ipa, dsra = pa[IP], pa[DSRPacket]
        ipb, dsrb = pb[IP], pb[DSRPacket]
        # check IP/DSR fields
        same = True
        same &= (ipa.src==ipb.src) and (ipa.dst==ipb.dst)
        same &= (ipa.id==ipb.id) and (ipa.proto==ipb.proto)
        if (ipa.chksum is not None) and (ipb.chksum is not None):
            same &= (ipa.chksum==ipb.chksum)
        same &= (dsra.length==dsrb.length)
        same &= (dsra.nextheader==dsrb.nextheader)
        same &= (len(dsra.options)==len(dsrb.options))
        return same

    ##############################
    # ROUTE TABLE METHODS
    ##############################
    def srcroute(self, *args, **kwargs):
        """Get source route from route cache."""
        return self.table.srcroute(*args, **kwargs)

    def removelink(self, a, b):
        """Remove link from route cache."""
        return self.table.removelink(a, b, self.address)

    ##############################
    # PROPERTY METHODS
    ##############################
    def get_mac(self):
        """Get MAC corresponding to this module."""
        if isinstance(self.__mac, MAC): return self.__mac
        # otherwise try to get MAC from ARP
        mac, arp = None, self.TXD.target.parent
        if isinstance(arp, ARP):
            if self in arp.listen: mac, f = arp.listen[self]
        # final check on MAC
        if not isinstance(mac, MAC): mac = None
        return mac

    def set_mac(self, mac):
        """Set MAC corresponding to this module."""
        m = None
        if isinstance(mac, MAC): m = mac
        self.__mac = m

    def get_promiscuous(self):
        """Get `MAC.promiscuous` flag."""
        p = None
        if self.mac: p = self.mac.promiscuous
        return p

    ##############################
    # LOGGING METHODS
    ##############################
    def get_ip_anno(self, p):
        """Internal method to get relevant annotations for an IP+DSR packet."""
        kwargs = Routing.get_ip_anno(self, p)
        kwargs.update(self.get_dsr_anno(p) )
        kwargs.update(self.get_agt_anno(p) )
        return kwargs

    def get_agt_anno(self, p):
        """Internal method to get relevant annotations for a AGT packet."""
        kwargs = {}
        isagt = isinstance(p, Packet) and p.haslayer(AGT)
        if not isagt: return kwargs
        agt = p[AGT]
        kwargs['agt-root'] = agt.traceid
        return kwargs

    def get_dsr_anno(self, p):
        """Internal method to get relevant annotations for a DSR packet."""
        kwargs = {}
        isdsr = isinstance(p, Packet) and p.haslayer(DSRPacket)
        if not isdsr: return kwargs
        dsr = p[DSRPacket]
        kwargs['dsr.nextheader'] = dsr.nextheader
        # classify based on DSR option
        rreq, rrep     = getdsr_rreq(dsr), getdsr_rrep(dsr)
        rerr, srcroute = getdsr_rerr(dsr), getdsr_srcroute(dsr)
        # get parameters from options
        if rreq:
            kwargs['rreq.ID'] = rreq.identification
            kwargs['rreq.target'] = rreq.target
            kwargs['rreq.addresses'] = rreq.addresses
        if rrep:
            kwargs['rrep.addresses'] = rrep.addresses
        if srcroute:
            kwargs['srcroute.segsleft'] = srcroute.segsleft
            kwargs['srcroute.addresses'] = srcroute.addresses
        if rerr:
            kwargs['rerr.errortype'] = _dsr_error_types[rerr.errortype]
            kwargs['rerr.err_src'] = rerr.err_src
            kwargs['rerr.err_dst'] = rerr.err_dst
            kwargs['rerr.type_specific'] = rerr.type_specific
        # get other parameters
        if p.hasanno('phy-rate'):
            kwargs['phy-rate'] = p.getanno('phy-rate')
        return kwargs

    def debug(self, *args, **kwargs):
        """Log debug statements to trace."""
        if DSRDEBUG:
            self.log(*args, **kwargs)

    def safe(self, x):
        """Print trace to standard out before throwing exception."""
        if not x:
            self.trace.output()
        return x
示例#39
0
文件: arf.py 项目: reidlindsay/wins
class ARF(DCF):
    """Auto Rate Fallback (ARF) protocol.

    Implementation based on description from Kamerman and Monteban (1997). By
    default ARF operates using CSMA/CA (i.e. not MACAW). To enable RTS/CTS
    messages, set `usecsma` to False.

    ARF Timeout
    ===========
    The ARF timer is used to indicate that the rate of transmissions should be
    increased prior to having `nsuccess` consecutive ACKs. This mechanism is
    meant to allow ARF to explore higher data rates when traffic is sparse.

    The timer can be implemented as a time-based or packet-based timer. The
    time-based timer is a straight forward timeout timer that waits for a
    duration before firing and indicating that the rate of the transmission
    should be increased. The packet-based timer uses the "virtual timer"
    approach described by Qiao, Choi, and Shin (2002). This "timer" counts the
    number of transmission attempts made rather than using a time-based timer.

    This module implements the ARF timer as a time-based timer.

    :CVariables:
     * `base`: Property to access base rate enumeration from `ra`.
     * `rates`: Property to access ordered list of valid rate enumerations. This
                is ordered based on the data rates reported by `get_datarate()`.

    :IVariables:
     * `ra`: `RateAdapt` module.
     * `ackcount`: Dictionary containing number of consecutive successful ACKs
                   received (negative values indicate consecutive dropped ACKs).
                   Values are keyed by destination address.
     * `arftimer`: Dictionary containing ARF timers used for updating transmit
                   data rate. Values are keyed by destination address. This
                   module implements ARF timers as packet-based timers.
     * `probation`: Dictionary containing probation flags indicating if
                    probation period has been entered; values are keyed by
                    destination address.
     * `nsuccess`: Threshold for number of consecutive ACKs that must be
                   received prior to increasing the data rate.
     * `nfailure`: Threshold for number of consecutive ACK failures that
                   will trigger a decrease in the data rate.
     * `timeout`: Timeout value for ARF timer; if exceeded, then increase rate.

    """
    name = "Auto Rate Fallback"
    tracename = "ARF"
    def __init__(self, usecsma=True, **kwargs):
        """Constructor."""
        # create ARF parameters
        self.nsuccess = None
        self.nfailure = None
        self.timeout  = None
        self.ackcount = {}
        self.arftimer = {}
        self.probation = {}
        self._rates = None
        # create ARF events
        self.dropack = SimEvent()
        self.recvack = SimEvent()
        DCF.__init__(self, usecsma=usecsma, **kwargs)
        # update event names
        self.dropack.name = "%s%s"%(self.name, ".dropack")
        self.recvack.name = "%s%s"%(self.name, ".recvack")

    base = property(fget=lambda self: self.ra.base)
    rates = property(fget=lambda self: self.get_rates() )

    def configure(self, base=None, nsuccess=None, nfailure=None, timeout=None, **kwargs):
        """Configure rate adaptation parameters.

        :param base: Base rate used to initialize `RateAdapt` component [default=0].
        :param nsuccess: Threshold for number of consecutive ACKs that must be
                         received prior to increasing the data rate.
        :param nfailure: Threshold for number of consecutive ACK failures that
                         will trigger a decrease in the data rate.
        :param timeout: Timeout that can automatically trigger
        """
        DCF.configure(self, **kwargs)
        if base is None: base = 0
        if nsuccess is None: nsuccess = ARF_NSUCCESS
        if nfailure is None: nfailure = ARF_NFAILURE
        if timeout  is None: timeout  = ARF_TIMEOUT
        ra = self.newchild("ra", RateAdapt, base=base, tracename=self.tracename+".RA")
        ra.set_rate(self.broadcast) # use default base rate for broadcast
        # create FSM to manage ARF
        fsm = self.newchild("manager", FSM, tracename=self.tracename+".MGR")
        fsm.goto(self.MGR)
        # set other parameters
        self.nsuccess = nsuccess
        self.nfailure = nfailure
        self.timeout  = timeout
        self.ackcount = {}
        self.arftimer = {}
        self.probation = {}

    def MGR(self, fsm):
        """MGR state; manage ARF protocol."""
        yield waitevent, fsm, (self.dropack, self.recvack)
        dropack = (self.dropack in fsm.eventsFired)
        recvack = (self.recvack in fsm.eventsFired)
        # get destination parameter from event
        if dropack: dst = self.dropack.signalparam
        if recvack: dst = self.recvack.signalparam
        errmsg = "[ARF]: Invalid state transition!"
        assert (dropack ^ recvack), errmsg
        # initialize ackcount if needed
        if (dst not in self.ackcount):
            self.ackcount[dst] = 0
            self.probation[dst] = False
            self.arftimer[dst] = 0
        nack = self.ackcount[dst]
        probation = self.probation[dst]
        timer = 0
        # check probation condition
        errmsg = "[ARF]: ACK count must be zero in probation!"
        assert not ((nack<>0) and probation), errmsg
        # increment arftimer for every transmission attempt (packet-based timer)
        timer += 1
        # process drop ACK or recv ACK
        if (nack>0):
            if dropack:     nack = -1
            elif recvack:   nack += 1
        elif (nack<0):
            if dropack:     nack -= 1
            elif recvack:   nack = +1
        else:
            if dropack:     nack = -1
            elif recvack:   nack = +1
            # probation? dropack -> decrease rate and continue using lower rate
            if probation and dropack:
                nack, timer, rate = 0, 0, self.decrate(dst)
                self.log("DECRATE", rate=rate, dst=dst, ackcount=nack, timer=timer, probation=probation)
            probation =  False      # reset flag
        # log events
        if dropack: self.log("DROPACK", dst=dst, ackcount=nack, arftimer=timer)
        if recvack: self.log("RECVACK", dst=dst, ackcount=nack, arftimer=timer)
        # check if Nsuccess or Nfailure condition is met
        timerexpired = (timer>self.timeout)
        if (self.nsuccess-nack<1) or timerexpired:
            # (Nack >= +Nsuccess) OR (Timer expired)
            #   -> increase rate, enter probation period
            rate = self.incrate(dst)
            nack, timer = 0, 0      # reinitialize ackcount and timer
            probation = True        # set probation flag
            self.log("INCRATE", rate=rate, dst=dst, ackcount=nack, probation=True)
        elif (nack+self.nfailure<1):
            # Nack <= -Nfailure -> decrease rate
            rate = self.decrate(dst)
            nack, timer = 0, 0     # set ackcount to 0, reset timer
            self.log("DECRATE", rate=rate, dst=dst, ackcount=nack, probation=probation)
        # set ackcount/probation value
        self.ackcount[dst]  = nack
        self.arftimer[dst]  = timer
        self.probation[dst] = probation
        # continue in MGR
        yield fsm.goto(self.MGR)

    def retry(self, *args, **kwargs):
        """Overloaded to manage dropped ACKs."""
        rc = self.retrycount
        DCF.retry(self, *args, **kwargs)
        # Actual retry?
        if (self.retrycount>rc) and self.isdot11data(self.datatosend):
            # retry count was increased -> dropped ACK
            data = self.get_dot11data(self.datatosend)
            dst, src  = data.addr1, data.addr2
            #self.log("DROPACK", dst=dst, src=src)
            self.dropack.signal(dst)

    def send_data(self, data):
        """Additional processing for outgoing DATA."""
        DCF.send_data(self, data)
        dst  = data.addr1
        rate = self.ra.get_rate(dst)
        data.setanno('phy-rate', rate)

    def recv_ack(self, ack):
        """Additional processing for incoming ACK."""
        DCF.recv_ack(self, ack)
        # Expecting ACK?
        if self.isdot11data(self.datatosend):
            data = self.get_dot11data(self.datatosend)
            dst, src = data.addr1, data.addr2
            # ACK for me? -> received ACK
            if (src==ack.addr1):
                #self.log("RECVACK", ack, dst=dst, src=src)
                self.recvack.signal(dst)

    def incrate(self, dst):
        """Increase data rate corresponding to destination `dst`."""
        rate = self.ra.get_rate(dst)
        errmsg = "[ARF]: Invalid rate (%s) in incrate()!"%(rate)
        assert (rate in self.rates), errmsg
        idx = self.rates.index(rate)
        if (idx+1<len(self.rates)):
            rate = self.rates[idx+1]
        self.ra.set_rate(dst, rate)
        return rate

    def decrate(self, dst):
        """Decrease data rate corresponding to destination `dst`."""
        rate = self.ra.get_rate(dst)
        errmsg = "[ARF]: Invalid rate (%s) in incrate()!"%(rate)
        assert (rate in self.rates), errmsg
        idx = self.rates.index(rate)
        if (idx>0):
            rate = self.rates[idx-1]
        self.ra.set_rate(dst, rate)
        return rate

    def get_rates(self, force=False):
        """Get list of valid rate enumerations.

        :param force: If true, recalculate rates; otherwise use cached value
                      [default=False].
        :return: List of valid rate enumerations.
        """
        if force: self._rates = None
        # initialize ordered rates
        if self._rates is None:
            orates = []     # ordered rates
            rates = [r for r in self.phy.rate]
            while rates:
                maxrate, maxidx = None, None
                for k in range(len(rates)):
                    r = rates[k]
                    rbps = self.get_datarate(r)
                    if maxrate is None: maxrate, maxidx = r, k
                    mbps = self.get_datarate(maxrate)
                    if (rbps>mbps):  maxrate, maxidx = r, k
                orates.insert(0, rates.pop(maxidx))     # add max rate to orates
            self._rates = orates
        return self._rates

    def log_send(self, p, *args, **kwargs):
        """Updated to print `ARF` related parameters."""
        if p.hasanno('phy-rate'):
            kwargs['phy-rate'] = p.getanno('phy-rate')
        DCF.log_send(self, p, *args, **kwargs)

    def log_recv(self, p, *args, **kwargs):
        """Updated to print `ARF` related parameters."""
        if p.hasanno('phy-rate'):
            kwargs['phy-rate'] = p.getanno('phy-rate')
        DCF.log_recv(self, p, *args, **kwargs)
示例#40
0
class StorageNode(IDable, Thread, RTI):
    """Base storage node."""
    def __init__(self, cnode, ID, configs):
        IDable.__init__(self, '%s/sn%s' % (cnode.ID.split('/')[0], ID))
        Thread.__init__(self)
        RTI.__init__(self, self.ID)
        self.logger = logging.getLogger(self.__class__.__name__)
        self.system = cnode.system
        self.configs = configs
        self.cnode = cnode
        self.maxNumTxns = configs.get('max.num.txns.per.storage.node', 1024)
        self.pool = Resource(self.maxNumTxns, name='pool', unitName='thread')
        self.groups = {}  #{gid : group}
        self.newTxns = []
        self.txnsRunning = set([])
        self.shouldClose = False
        self.monitor = Profiler.getMonitor(self.ID)
        self.M_POOL_WAIT_PREFIX = '%s.pool.wait' % self.ID
        self.M_TXN_RUN_PREFIX = '%s.txn.run' % self.ID
        self.M_NUM_TXNS_RUN_KEY = '%s.num.txns' % self.ID
        self.runningThreads = set([])
        self.closeEvent = SimEvent()
        self.newTxnEvent = SimEvent()

    @property
    def load(self):
        return len(self.txnsRunning) + len(self.newTxns)

    def close(self):
        self.logger.info('Closing %s at %s' % (self, now()))
        self.shouldClose = True
        self.closeEvent.signal()

    def onTxnArrive(self, txn):
        self.newTxns.append(txn)
        self.newTxnEvent.signal()

    def onTxnsArrive(self, txns):
        self.newTxns.extend(txns)
        self.newTxnEvent.signal()

    def newTxnRunner(self, txn):
        class DefaultTxnRunner(Thread):
            def __init__(self, snode, txn):
                Thread.__init__(self)
                self.snode = snode
                self.txn = txn
                self.logger = logging.getLogger(self.__class__.__name__)

            def run(self):
                self.logger.debug('Running transaction %s at %s' %
                                  (txn.ID, now()))
                yield hold, self, RandInterval.get('expo', 100).next()

        return DefaultTxnRunner(self, txn)

    class TxnStarter(Thread):
        def __init__(self, snode, txn):
            Thread.__init__(self)
            self.snode = snode
            self.txn = txn

        def run(self):
            #add self and txn to snode
            self.snode.runningThreads.add(self)
            #wait for pool thread resource if necessary
            #self.snode.logger.debug(
            #    '%s start txn=%s, running=%s, outstanding=%s'
            #    %(self.snode, self.txn.ID,
            #      '(%s)'%(','.join([t.ID for t in self.snode.txnsRunning])),
            #      '(%s)'%(','.join([t.ID for t in self.snode.newTxns]))
            #     ))
            self.snode.monitor.start(
                '%s.%s' % (self.snode.M_POOL_WAIT_PREFIX, self.txn.ID))
            yield request, self, self.snode.pool
            self.snode.monitor.stop(
                '%s.%s' % (self.snode.M_POOL_WAIT_PREFIX, self.txn.ID))
            #start runner and wait for it to finish
            thread = self.snode.newTxnRunner(self.txn)
            assert self.txn not in self.snode.txnsRunning, \
                    '%s already started txn %s'%(self.snode, self.txn)
            self.snode.txnsRunning.add(self.txn)
            self.snode.monitor.observe(self.snode.M_NUM_TXNS_RUN_KEY,
                                       len(self.snode.txnsRunning))
            self.snode.monitor.start(
                '%s.%s' % (self.snode.M_TXN_RUN_PREFIX, self.txn.ID))
            thread.start()
            yield waitevent, self, thread.finish
            self.snode.monitor.stop('%s.%s' %
                                    (self.snode.M_TXN_RUN_PREFIX, self.txn.ID))
            yield release, self, self.snode.pool
            #clean up
            self.snode.txnsRunning.remove(self.txn)
            self.snode.runningThreads.remove(self)
            self.snode.cnode.onTxnDepart(self.txn)

    def run(self):
        #the big while loop
        while True:
            yield waitevent, self, self.newTxnEvent
            while len(self.newTxns) > 0:
                #pop from new txn to running txn
                txn = self.newTxns.pop(0)
                #start
                thread = StorageNode.TxnStarter(self, txn)
                thread.start()
示例#41
0
 def __init__(self, name):
     Process.__init__(self, name=name)
     self.doneSignal = SimEvent()
示例#42
0
class NAVTimer(Element):
    """Provides API to maintain network allocation vector and associated virtual
    carrier sense timer.

    :CVariables:
     * `timer`: Internal `Timer` object.
     * `done`: SimPy Event signalled when NAV timer successfully expires.
     * `kill`: SimPy Event signalled when NAV timer stops/pauses unexpectedly.
     * `fired`: Check if NAV timer (i.e. `timer`) has fired.
     * `running`: Check if NAV timer is still running.
     * `stopped`: Check if NAV timer has been stopped.
    """
    name = "nav timer"
    tracename = "NAV"
    def __init__(self, **kwargs):
        """Constructor."""
        self.__timer = None
        Element.__init__(self, **kwargs)

    timer = property(fget=lambda self: self.__timer)
    done = property(fget=lambda self: self.timer.done)
    kill = property(fget=lambda self: self.timer.kill)
    fired = property(fget=lambda self: \
                     isinstance(self.timer,Timer) and self.timer.fired)
    running = property(fget=lambda self: \
                       isinstance(self.timer,Timer) and self.timer.running)
    stopped = property(fget=lambda self: \
                       isinstance(self.timer,Timer) and self.timer.stopped)

    def configure(self, **kwargs):
        """Initialize `timer` to None and create monitor."""
        self.__timer = None
        self.__wakeup = SimEvent(name=self.name+".wake")
        mon = self.newchild("mon", FSM, tracename=self.tracename+".MON")
        #mon.goto(self.MON)
        mon.goto(self.MONIDLE)

    def update(self, proc, t=None):
        """Blocking call to cancel active timer, if needed, and starts a new
        timer.

        :param t: New timer value.
        :return: Blocking clause.

        If `t` is None, this method will just cancel any active timer and reset
        `timer` to None.
        """
        # cancel active timer
        if isinstance(self.timer, Timer):
            self.timer.halt()
            #self.delchild("navtimer")
            self.__timer = None
        # start new timer
        if (t>0):
            self.__timer = self.newchild("navtimer", Timer, t, start=True, \
                                         tracename=self.tracename+".TIMER")
            self.__wakeup.signal(self.timer)
        return hold, proc, 0

    def reset(self):
        """Non-blocking call to cancel active timer."""
        return self.update(None, None)

    def MONIDLE(self, fsm):
        """MONIDLE state; monitor `timer` when NAV Timer is in IDLE state."""
        # wait for timer to be set
        self.log_idle()
        yield waitevent, fsm, self.__wakeup
        t = self.__wakeup.signalparam
        assert isinstance(t, Timer)
        yield fsm.goto(self.MONBUSY, t)

    def MONBUSY(self, fsm, t):
        """MONIDLE state; monitor `timer` when NAV Timer is in BUSY state."""
        self.log_busy(duration=time2usec(t.duration) )
        yield waitevent, fsm, (t.done, t.kill)
        # timer fired
        if (t.done in fsm.eventsFired):
            pass
        # timer stopped
        elif (t.kill in fsm.eventsFired) and t.stopped:
            pass
        # otherwise -> raise exception
        else:
            raise RuntimeError, "[NAVTIMER]: Monitor indicates " + \
                                "NAV timer paused unexpectedly!"
        # done monitoring timer
        yield fsm.goto(self.MONIDLE)

    def MON(self, fsm):
        """MON state; monitor `timer` events."""
        while fsm.active():
            # wait for timer to be set
            self.log_idle()
            yield waitevent, fsm, self.__wakeup
            t = self.__wakeup.signalparam
            # monitor timer events
            while isinstance(t, Timer):
                self.log_busy(duration=time2usec(t.duration) )
                yield waitevent, fsm, (t.done, t.kill)
                # timer fired
                if (t.done in fsm.eventsFired):
                    pass
                # timer stopped
                elif (t.kill in fsm.eventsFired) and t.stopped:
                    pass
                # otherwise -> raise exception
                else:
                    raise RuntimeError, "[NAVTIMER]: Monitor indicates " + \
                            "NAV timer paused unexpectedly!"
                # done monitoring timer
                t = None
        return

    def log_busy(self, **kwargs):
        """Log NAV busy."""
        self.log("NAVBUSY", **kwargs)

    def log_idle(self, **kwargs):
        """Log NAV idle."""
        self.log("NAVIDLE", **kwargs)

    def log(self, evt=None, p=None, *args, **kwargs):
        """Overloaded to check verbose level and set common annotations."""
        force = False
        if ('verbose' in kwargs): force = (kwargs['verbose']>NAV_VERBOSE)
        if self.verbose>NAV_VERBOSE or force:
            Element.log(self, evt, p, *args, **kwargs)
示例#43
0
文件: fsm.py 项目: reidlindsay/wins
class Timer(FSM):
    """Stopwatch timer that fires an event after waiting for a specified time.

    After starting `Timer`, use `pause()`, `resume()`, and `stop()` to control
    the execution of the timer. The `done` and `kill` signals to monitor the
    completion of the `Timer`.

    :CVariables:
     * `CMDPAUSE`: Internal constant for pause command.
     * `CMDRESUME`: Internal constant for resume command.
     * `duration`: Property to access time that `Timer` should run.
     * `timepassed`: Property to access time elapsed by `Timer`.
     * `timeleft`: Property to access time left on `Timer`.
     * `ispaused`: Property to check if timer is currently paused.
     * `fired`: Property to check if `done` occurred.
     * `running`: Property to check if `started` and currently running.
     * `stopped`: Property to check if not `fired` and no longer active.
     * `ctrlQ`: Internal `Queue` for control messages.

    :IVariables:
     * `done`: SimEvent signalled when timer finishes successfully.
     * `kill`: SimEvent signalled when timer is prematurely stopped.
    """
    name="timer"
    tracename="TIMER"
    CMDPAUSE  = "pause"
    CMDRESUME = "resume"
    def __init__(self, duration, start=False, initstate=None, **kwargs):
        """Constructor.

        :param duration: Time for which `Timer` should run.
        :param start: Boolean; if true, `start()` immediately.
        :param initstate: Depricated (do not use).
        :param kwargs: Keywords passed to `FSM` constructor.
        """
        assert (duration>0), "[TIMER]: Cannot simulate non-positive duration!"
        FSM.__init__(self, start=False, initstate=self.RUN, **kwargs)
        self.__tpassed = 0
        self.__duration  = duration
        self.__ctrlQ = Queue()
        self.__tic   = None
        self.done = SimEvent(name=self.name+".done")
        self.kill = SimEvent(name=self.name+".kill")
        if start: self.start()

    duration   = property(fget=lambda self: self.__duration)
    timepassed = property(fget=lambda self: self.__timepassed() )
    timeleft   = property(fget=lambda self: self.duration - self.timepassed)
    ispaused   = property(fget=lambda self: (self.state==self.PAUSE) )
    fired      = property(fget=lambda self: self.done.occurred)
    running    = property(fget=lambda self: \
                          self.started and (self.state==self.RUN) )
    stopped    = property(fget=lambda self: \
                          (not self.fired) and (self.state==self.HALT) )
    ctrlQ      = property(fget=lambda self: self.__ctrlQ)

    def RUN(self, fsm):
        """RUN state; timer is active.

        Call `pause()` to pause an active timer. This method will signal `done`
        upon completion.
        """
        # set parameters
        tstart = now()
        self.__tic = tstart      # temporarily store tic as start of RUN
        queue  = self.ctrlQ
        tleft  = self.duration - self.__tpassed  # time left on timer
        # wait for timer to expire (or be paused)
        yield queue.remove(fsm, 1, renege=tleft)
        self.__tic = None
        telapsed = now() - tstart
        self.__tpassed += telapsed
        if fsm.acquired(queue):
            # PAUSE command
            assert (telapsed<tleft), \
                    "[TIMER]: Elapsed time exceeded time left during RUN!"
            assert (len(fsm.got)==1), "[TIMER]: Control queue failed!"
            cmd = fsm.got[0]
            assert (cmd==self.CMDPAUSE), \
                   "[TIMER]: Invalid control command received in RUN!"
            yield fsm.goto(self.PAUSE)
        else:
            assert (abs(self.__tpassed-self.duration)<const.EPSILON), \
                    "[TIMER]: Timer failed to complete properly in RUN!"
            self.__tpassed = self.duration
            if self.verbose>TIMER_VERBOSE: self.log("DONE")
            self.done.signal()
        yield fsm.goto(self.HALT, force=False)

    def PAUSE(self, fsm):
        """PAUSE state; timer is paused.

        Call `resume()` to restart timer.
        """
        queue = self.ctrlQ
        yield queue.remove(fsm, 1)
        assert fsm.acquired(queue) and (len(fsm.got)==1), \
               "[TIMER]: PAUSE failed to dequeue control message!"
        cmd = fsm.got[0]
        assert (cmd==self.CMDRESUME), \
                "[TIMER]: Invalid control command received in PAUSE!"
        yield fsm.goto(self.RUN)

    def resume(self, proc):
        """Blocking call to restart timer if in `PAUSE` state.

        :param proc: Process to block on resume command.
        """
        if (self.state==self.PAUSE):
            return self.ctrlQ.insert(proc, [self.CMDRESUME])
        else:
            return hold, proc, 0

    def pause(self, proc):
        """Blocking call to pause timer if in `RUN` state.

        :param proc: Process to block on pause command.
        """
        if (self.state==self.RUN):
            return self.ctrlQ.insert(proc, [self.CMDPAUSE])
        else:
            return hold, proc, 0

    def __timepassed(self):
        """Private method to determine time elapsed on timer."""
        if self.__tic is None:
            return self.__tpassed
        else:
            tdelta = now() - self.__tic
            return (self.__tpassed + tdelta)

    def HALT(self, fsm, force=True):
        """Overload `HALT` state to signal `kill` if needed."""
        if (force or (self.timeleft>0)):
            timepassed = self.timepassed
            if self.verbose>TIMER_VERBOSE:
                self.log("CANCEL", timepassed=time2usec(timepassed, fmt="%.4g"), \
                                   timeleft=self.timeleft, force=force)
            self.kill.signal(timepassed)
        yield hold, fsm, 0

    def log(self, evt=None, p=None, *args, **kwargs):
        """Overloaded to check verbose level and set common annotations."""
        force = False
        if ('verbose' in kwargs): force = (kwargs['verbose']>TIMER_VERBOSE)
        if self.verbose>TIMER_VERBOSE or force:
            FSM.log(self, evt, p, *args, **kwargs)