mirror of
				https://github.com/JoelBender/bacpypes
				synced 2025-10-27 00:57:47 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			1298 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1298 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| 
 | |
| """
 | |
| IO Module
 | |
| """
 | |
| 
 | |
| import sys
 | |
| import logging
 | |
| 
 | |
| from time import time as _time
 | |
| 
 | |
| import threading
 | |
| import cPickle
 | |
| from bisect import bisect_left
 | |
| from collections import deque
 | |
| 
 | |
| from bacpypes.debugging import bacpypes_debugging, DebugContents, ModuleLogger
 | |
| 
 | |
| from bacpypes.core import deferred
 | |
| 
 | |
| from bacpypes.comm import PDU, Client, bind
 | |
| from bacpypes.task import FunctionTask
 | |
| from bacpypes.udp import UDPDirector
 | |
| 
 | |
| # some debugging
 | |
| _debug = 0
 | |
| _log = ModuleLogger(globals())
 | |
| _commlog = logging.getLogger(__name__ + "._commlog")
 | |
| 
 | |
| #
 | |
| #   IOCB States
 | |
| #
 | |
| 
 | |
| IDLE = 0        # has not been submitted
 | |
| PENDING = 1     # queued, waiting for processing
 | |
| ACTIVE = 2      # being processed
 | |
| COMPLETED = 3   # finished
 | |
| ABORTED = 4     # finished in a bad way
 | |
| 
 | |
| _stateNames = {
 | |
|     IDLE: 'IDLE',
 | |
|     PENDING: 'PENDING',
 | |
|     ACTIVE: 'ACTIVE',
 | |
|     COMPLETED: 'COMPLETED',
 | |
|     ABORTED: 'ABORTED',
 | |
|     }
 | |
| 
 | |
| #
 | |
| #   IOQController States
 | |
| #
 | |
| 
 | |
| CTRL_IDLE = 0       # nothing happening
 | |
| CTRL_ACTIVE = 1     # working on an iocb
 | |
| CTRL_WAITING = 1    # waiting between iocb requests (throttled)
 | |
| 
 | |
| _ctrlStateNames = {
 | |
|     CTRL_IDLE: 'IDLE',
 | |
|     CTRL_ACTIVE: 'ACTIVE',
 | |
|     CTRL_WAITING: 'WAITING',
 | |
|     }
 | |
| 
 | |
| # dictionary of local controllers
 | |
| _local_controllers = {}
 | |
| _proxy_server = None
 | |
| 
 | |
| # special abort error
 | |
| TimeoutError = RuntimeError("timeout")
 | |
| 
 | |
| #
 | |
| #   _strftime
 | |
| #
 | |
| 
 | |
| def _strftime():
 | |
|     return "%011.6f" % (_time() % 3600,)
 | |
| 
 | |
| #
 | |
| #   IOCB - Input Output Control Block
 | |
| #
 | |
| 
 | |
| _identNext = 1
 | |
| _identLock = threading.Lock()
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class IOCB(DebugContents):
 | |
| 
 | |
|     _debug_contents = \
 | |
|         ( 'args', 'kwargs'
 | |
|         , 'ioState', 'ioResponse-', 'ioError'
 | |
|         , 'ioController', 'ioServerRef', 'ioControllerRef', 'ioClientID', 'ioClientAddr'
 | |
|         , 'ioComplete', 'ioCallback+', 'ioQueue', 'ioPriority', 'ioTimeout'
 | |
|         )
 | |
| 
 | |
|     def __init__(self, *args, **kwargs):
 | |
|         global _identNext
 | |
| 
 | |
|         # lock the identity sequence number
 | |
|         _identLock.acquire()
 | |
| 
 | |
|         # generate a unique identity for this block
 | |
|         ioID = _identNext
 | |
|         _identNext += 1
 | |
| 
 | |
|         # release the lock
 | |
|         _identLock.release()
 | |
| 
 | |
|         # debugging postponed until ID acquired
 | |
|         if _debug: IOCB._debug("__init__(%d) %r %r", ioID, args, kwargs)
 | |
| 
 | |
|         # save the ID
 | |
|         self.ioID = ioID
 | |
| 
 | |
|         # save the request parameters
 | |
|         self.args = args
 | |
|         self.kwargs = kwargs
 | |
| 
 | |
|         # start with an idle request
 | |
|         self.ioState = IDLE
 | |
|         self.ioResponse = None
 | |
|         self.ioError = None
 | |
| 
 | |
|         # blocks are bound to a controller
 | |
|         self.ioController = None
 | |
| 
 | |
|         # blocks could reference a local or remote server
 | |
|         self.ioServerRef = None
 | |
|         self.ioControllerRef = None
 | |
|         self.ioClientID = None
 | |
|         self.ioClientAddr = None
 | |
| 
 | |
|         # each block gets a completion event
 | |
|         self.ioComplete = threading.Event()
 | |
|         self.ioComplete.clear()
 | |
| 
 | |
|         # applications can set a callback functions
 | |
|         self.ioCallback = []
 | |
| 
 | |
|         # request is not currently queued
 | |
|         self.ioQueue = None
 | |
| 
 | |
|         # extract the priority if it was given
 | |
|         self.ioPriority = kwargs.get('_priority', 0)
 | |
|         if '_priority' in kwargs:
 | |
|             if _debug: IOCB._debug("    - ioPriority: %r", self.ioPriority)
 | |
|             del kwargs['_priority']
 | |
| 
 | |
|         # request has no timeout
 | |
|         self.ioTimeout = None
 | |
| 
 | |
|     def add_callback(self, fn, *args, **kwargs):
 | |
|         """Pass a function to be called when IO is complete."""
 | |
|         if _debug: IOCB._debug("add_callback(%d) %r %r %r", self.ioID, fn, args, kwargs)
 | |
| 
 | |
|         # store it
 | |
|         self.ioCallback.append((fn, args, kwargs))
 | |
| 
 | |
|         # already complete?
 | |
|         if self.ioComplete.isSet():
 | |
|             self.trigger()
 | |
| 
 | |
|     def wait(self, *args):
 | |
|         """Wait for the completion event to be set."""
 | |
|         if _debug: IOCB._debug("wait(%d) %r", self.ioID, args)
 | |
| 
 | |
|         # waiting from a non-daemon thread could be trouble
 | |
|         self.ioComplete.wait(*args)
 | |
| 
 | |
|     def trigger(self):
 | |
|         """Set the event and make the callback."""
 | |
|         if _debug: IOCB._debug("trigger(%d)", self.ioID)
 | |
| 
 | |
|         # if it's queued, remove it from its queue
 | |
|         if self.ioQueue:
 | |
|             if _debug: IOCB._debug("    - dequeue")
 | |
|             self.ioQueue.remove(self)
 | |
| 
 | |
|         # if there's a timer, cancel it
 | |
|         if self.ioTimeout:
 | |
|             if _debug: IOCB._debug("    - cancel timeout")
 | |
|             self.ioTimeout.suspend_task()
 | |
| 
 | |
|         # set the completion event
 | |
|         self.ioComplete.set()
 | |
| 
 | |
|         # make the callback
 | |
|         for fn, args, kwargs in self.ioCallback:
 | |
|             if _debug: IOCB._debug("    - callback fn: %r %r %r", fn, args, kwargs)
 | |
|             fn(self, *args, **kwargs)
 | |
| 
 | |
|     def complete(self, msg):
 | |
|         """Called to complete a transaction, usually when process_io has
 | |
|         shipped the IOCB off to some other thread or function."""
 | |
|         if _debug: IOCB._debug("complete(%d) %r", self.ioID, msg)
 | |
| 
 | |
|         if self.ioController:
 | |
|             # pass to controller
 | |
|             self.ioController.complete_io(self, msg)
 | |
|         else:
 | |
|             # just fill in the data
 | |
|             self.ioState = COMPLETED
 | |
|             self.ioResponse = msg
 | |
|             self.trigger()
 | |
| 
 | |
|     def abort(self, err):
 | |
|         """Called by a client to abort a transaction."""
 | |
|         if _debug: IOCB._debug("abort(%d) %r", self.ioID, err)
 | |
| 
 | |
|         if self.ioController:
 | |
|             # pass to controller
 | |
|             self.ioController.abort_io(self, err)
 | |
|         elif self.ioState < COMPLETED:
 | |
|             # just fill in the data
 | |
|             self.ioState = ABORTED
 | |
|             self.ioError = err
 | |
|             self.trigger()
 | |
| 
 | |
|     def set_timeout(self, delay, err=TimeoutError):
 | |
|         """Called to set a transaction timer."""
 | |
|         if _debug: IOCB._debug("set_timeout(%d) %r err=%r", self.ioID, delay, err)
 | |
| 
 | |
|         # if one has already been created, cancel it
 | |
|         if self.ioTimeout:
 | |
|             self.ioTimeout.suspend_task()
 | |
|         else:
 | |
|             self.ioTimeout = FunctionTask(self.abort, err)
 | |
| 
 | |
|         # (re)schedule it
 | |
|         self.ioTimeout.install_task(_time() + delay)
 | |
| 
 | |
|     def __repr__(self):
 | |
|         xid = id(self)
 | |
|         if (xid < 0): xid += (1L << 32)
 | |
| 
 | |
|         sname = self.__module__ + '.' + self.__class__.__name__
 | |
|         desc = "(%d)" % (self.ioID,)
 | |
| 
 | |
|         return '<' + sname + desc + ' instance at 0x%08x' % (xid,) + '>'
 | |
| 
 | |
| #
 | |
| #   IOChainMixIn
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class IOChainMixIn(DebugContents):
 | |
| 
 | |
|     _debugContents = ( 'ioChain++', )
 | |
| 
 | |
|     def __init__(self, iocb):
 | |
|         if _debug: IOChainMixIn._debug("__init__ %r", iocb)
 | |
| 
 | |
|         # save a refence back to the iocb
 | |
|         self.ioChain = iocb
 | |
| 
 | |
|         # set the callback to follow the chain
 | |
|         self.add_callback(self.chain_callback)
 | |
| 
 | |
|         # if we're not chained, there's no notification to do
 | |
|         if not self.ioChain:
 | |
|             return
 | |
| 
 | |
|         # this object becomes its controller
 | |
|         iocb.ioController = self
 | |
| 
 | |
|         # consider the parent active
 | |
|         iocb.ioState = ACTIVE
 | |
| 
 | |
|         try:
 | |
|             if _debug: IOChainMixIn._debug("    - encoding")
 | |
| 
 | |
|             # let the derived class set the args and kwargs
 | |
|             self.Encode()
 | |
| 
 | |
|             if _debug: IOChainMixIn._debug("    - encode complete")
 | |
|         except:
 | |
|             # extract the error and abort the request
 | |
|             err = sys.exc_info()[1]
 | |
|             if _debug: IOChainMixIn._exception("    - encoding exception: %r", err)
 | |
| 
 | |
|             iocb.abort(err)
 | |
| 
 | |
|     def chain_callback(self, iocb):
 | |
|         """Callback when this iocb completes."""
 | |
|         if _debug: IOChainMixIn._debug("chain_callback %r", iocb)
 | |
| 
 | |
|         # if we're not chained, there's no notification to do
 | |
|         if not self.ioChain:
 | |
|             return
 | |
| 
 | |
|         # refer to the chained iocb
 | |
|         iocb = self.ioChain
 | |
| 
 | |
|         try:
 | |
|             if _debug: IOChainMixIn._debug("    - decoding")
 | |
| 
 | |
|             # let the derived class transform the data
 | |
|             self.Decode()
 | |
| 
 | |
|             if _debug: IOChainMixIn._debug("    - decode complete")
 | |
|         except:
 | |
|             # extract the error and abort
 | |
|             err = sys.exc_info()[1]
 | |
|             if _debug: IOChainMixIn._exception("    - decoding exception: %r", err)
 | |
| 
 | |
|             iocb.ioState = ABORTED
 | |
|             iocb.ioError = err
 | |
| 
 | |
|         # break the references
 | |
|         self.ioChain = None
 | |
|         iocb.ioController = None
 | |
| 
 | |
|         # notify the client
 | |
|         iocb.trigger()
 | |
| 
 | |
|     def abort_io(self, iocb, err):
 | |
|         """Forward the abort downstream."""
 | |
|         if _debug: IOChainMixIn._debug("abort_io %r %r", iocb, err)
 | |
| 
 | |
|         # make sure we're being notified of an abort request from
 | |
|         # the iocb we are chained from
 | |
|         if iocb is not self.ioChain:
 | |
|             raise RuntimeError, "broken chain"
 | |
| 
 | |
|         # call my own abort(), which may forward it to a controller or
 | |
|         # be overridden by IOGroup
 | |
|         self.abort(err)
 | |
| 
 | |
|     def encode(self):
 | |
|         """Hook to transform the request, called when this IOCB is
 | |
|         chained."""
 | |
|         if _debug: IOChainMixIn._debug("encode (pass)")
 | |
| 
 | |
|         # by default do nothing, the arguments have already been supplied
 | |
| 
 | |
|     def decode(self):
 | |
|         """Hook to transform the response, called when this IOCB is
 | |
|         completed."""
 | |
|         if _debug: IOChainMixIn._debug("decode")
 | |
| 
 | |
|         # refer to the chained iocb
 | |
|         iocb = self.ioChain
 | |
| 
 | |
|         # if this has completed successfully, pass it up
 | |
|         if self.ioState == COMPLETED:
 | |
|             if _debug: IOChainMixIn._debug("    - completed: %r", self.ioResponse)
 | |
| 
 | |
|             # change the state and transform the content
 | |
|             iocb.ioState = COMPLETED
 | |
|             iocb.ioResponse = self.ioResponse
 | |
| 
 | |
|         # if this aborted, pass that up too
 | |
|         elif self.ioState == ABORTED:
 | |
|             if _debug: IOChainMixIn._debug("    - aborted: %r", self.ioError)
 | |
| 
 | |
|             # change the state
 | |
|             iocb.ioState = ABORTED
 | |
|             iocb.ioError = self.ioError
 | |
| 
 | |
|         else:
 | |
|             raise RuntimeError, "invalid state: %d" % (self.ioState,)
 | |
| 
 | |
| #
 | |
| #   IOChain
 | |
| #
 | |
| 
 | |
| class IOChain(IOCB, IOChainMixIn):
 | |
| 
 | |
|     def __init__(self, chain, *args, **kwargs):
 | |
|         """Initialize a chained control block."""
 | |
|         if _debug: IOChain._debug("__init__ %r %r %r", chain, args, kwargs)
 | |
| 
 | |
|         # initialize IOCB part to pick up the ioID
 | |
|         IOCB.__init__(self, *args, **kwargs)
 | |
|         IOChainMixIn.__init__(self, chain)
 | |
| 
 | |
| #
 | |
| #   IOGroup
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class IOGroup(IOCB, DebugContents):
 | |
| 
 | |
|     _debugContents = ('ioMembers',)
 | |
| 
 | |
|     def __init__(self):
 | |
|         """Initialize a group."""
 | |
|         if _debug: IOGroup._debug("__init__")
 | |
|         IOCB.__init__(self)
 | |
| 
 | |
|         # start with an empty list of members
 | |
|         self.ioMembers = []
 | |
| 
 | |
|         # start out being done.  When an IOCB is added to the 
 | |
|         # group that is not already completed, this state will 
 | |
|         # change to PENDING.
 | |
|         self.ioState = COMPLETED
 | |
|         self.ioComplete.set()
 | |
| 
 | |
|     def add(self, iocb):
 | |
|         """Add an IOCB to the group, you can also add other groups."""
 | |
|         if _debug: IOGroup._debug("Add %r", iocb)
 | |
| 
 | |
|         # add this to our members
 | |
|         self.ioMembers.append(iocb)
 | |
| 
 | |
|         # assume all of our members have not completed yet
 | |
|         self.ioState = PENDING
 | |
|         self.ioComplete.clear()
 | |
| 
 | |
|         # when this completes, call back to the group.  If this
 | |
|         # has already completed, it will trigger
 | |
|         iocb.add_callback(self.group_callback)
 | |
| 
 | |
|     def group_callback(self, iocb):
 | |
|         """Callback when a child iocb completes."""
 | |
|         if _debug: IOGroup._debug("group_callback %r", iocb)
 | |
| 
 | |
|         # check all the members
 | |
|         for iocb in self.ioMembers:
 | |
|             if not iocb.ioComplete.isSet():
 | |
|                 if _debug: IOGroup._debug("    - waiting for child: %r", iocb)
 | |
|                 break
 | |
|         else:
 | |
|             if _debug: IOGroup._debug("    - all children complete")
 | |
|             # everything complete
 | |
|             self.ioState = COMPLETED
 | |
|             self.trigger()
 | |
| 
 | |
|     def abort(self, err):
 | |
|         """Called by a client to abort all of the member transactions.
 | |
|         When the last pending member is aborted the group callback
 | |
|         function will be called."""
 | |
|         if _debug: IOGroup._debug("abort %r", err)
 | |
| 
 | |
|         # change the state to reflect that it was killed
 | |
|         self.ioState = ABORTED
 | |
|         self.ioError = err
 | |
| 
 | |
|         # abort all the members
 | |
|         for iocb in self.ioMembers:
 | |
|             iocb.abort(err)
 | |
| 
 | |
|         # notify the client
 | |
|         self.trigger()
 | |
| 
 | |
| #
 | |
| #   IOQueue - Input Output Queue
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class IOQueue:
 | |
| 
 | |
|     def __init__(self, name):
 | |
|         if _debug: IOQueue._debug("__init__ %r", name)
 | |
| 
 | |
|         self.queue = []
 | |
|         self.notempty = threading.Event()
 | |
|         self.notempty.clear()
 | |
| 
 | |
|     def put(self, iocb):
 | |
|         """Add an IOCB to a queue.  This is usually called by the function
 | |
|         that filters requests and passes them out to the correct processing
 | |
|         thread."""
 | |
|         if _debug: IOQueue._debug("put %r", iocb)
 | |
| 
 | |
|         # requests should be pending before being queued
 | |
|         if iocb.ioState != PENDING:
 | |
|             raise RuntimeError, "invalid state transition"
 | |
| 
 | |
|         # save that it might have been empty
 | |
|         wasempty = not self.notempty.isSet()
 | |
| 
 | |
|         # add the request to the end of the list of iocb's at same priority
 | |
|         priority = iocb.ioPriority
 | |
|         item = (priority, iocb)
 | |
|         self.queue.insert(bisect_left(self.queue, (priority+1,)), item)
 | |
| 
 | |
|         # point the iocb back to this queue
 | |
|         iocb.ioQueue = self
 | |
| 
 | |
|         # set the event, queue is no longer empty
 | |
|         self.notempty.set()
 | |
| 
 | |
|         return wasempty
 | |
| 
 | |
|     def get(self, block=1, delay=None):
 | |
|         """Get a request from a queue, optionally block until a request
 | |
|         is available."""
 | |
|         if _debug: IOQueue._debug("get block=%r delay=%r", block, delay)
 | |
| 
 | |
|         # if the queue is empty and we do not block return None
 | |
|         if not block and not self.notempty.isSet():
 | |
|             return None
 | |
| 
 | |
|         # wait for something to be in the queue
 | |
|         if delay:
 | |
|             self.notempty.wait(delay)
 | |
|             if not self.notempty.isSet():
 | |
|                 return None
 | |
|         else:
 | |
|             self.notempty.wait()
 | |
| 
 | |
|         # extract the first element
 | |
|         priority, iocb = self.queue[0]
 | |
|         del self.queue[0]
 | |
|         iocb.ioQueue = None
 | |
| 
 | |
|         # if the queue is empty, clear the event
 | |
|         qlen = len(self.queue)
 | |
|         if not qlen:
 | |
|             self.notempty.clear()
 | |
| 
 | |
|         # return the request
 | |
|         return iocb
 | |
| 
 | |
|     def remove(self, iocb):
 | |
|         """Remove a control block from the queue, called if the request
 | |
|         is canceled/aborted."""
 | |
|         if _debug: IOQueue._debug("remove %r", iocb)
 | |
| 
 | |
|         # remove the request from the queue
 | |
|         for i, item in enumerate(self.queue):
 | |
|             if iocb is item[1]:
 | |
|                 if _debug: IOQueue._debug("    - found at %d", i)
 | |
|                 del self.queue[i]
 | |
| 
 | |
|                 # if the queue is empty, clear the event
 | |
|                 qlen = len(self.queue)
 | |
|                 if not qlen:
 | |
|                     self.notempty.clear()
 | |
| 
 | |
|                 break
 | |
|         else:
 | |
|             if _debug: IOQueue._debug("    - not found")
 | |
| 
 | |
|     def abort(self, err):
 | |
|         """abort all of the control blocks in the queue."""
 | |
|         if _debug: IOQueue._debug("abort %r", err)
 | |
| 
 | |
|         # send aborts to all of the members
 | |
|         try:
 | |
|             for iocb in self.queue:
 | |
|                 iocb.ioQueue = None
 | |
|                 iocb.abort(err)
 | |
| 
 | |
|             # flush the queue
 | |
|             self.queue = []
 | |
| 
 | |
|             # the queue is now empty, clear the event
 | |
|             self.notempty.clear()
 | |
|         except ValueError:
 | |
|             pass
 | |
| 
 | |
| #
 | |
| #   IOController
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class IOController:
 | |
| 
 | |
|     def __init__(self, name=None):
 | |
|         """Initialize a controller."""
 | |
|         if _debug: IOController._debug("__init__ name=%r", name)
 | |
| 
 | |
|         # save the name
 | |
|         self.name = name
 | |
| 
 | |
|         # register the name
 | |
|         if name is not None:
 | |
|             if name in _local_controllers:
 | |
|                 raise RuntimeError, "already a local controller called '%s': %r" % (name, _local_controllers[name])
 | |
|             _local_controllers[name] = self
 | |
| 
 | |
|     def abort(self, err):
 | |
|         """Abort all requests, no default implementation."""
 | |
|         pass
 | |
| 
 | |
|     def request_io(self, iocb):
 | |
|         """Called by a client to start processing a request."""
 | |
|         if _debug: IOController._debug("request_io %r", iocb)
 | |
| 
 | |
|         # bind the iocb to this controller
 | |
|         iocb.ioController = self
 | |
| 
 | |
|         try:
 | |
|             # hopefully there won't be an error
 | |
|             err = None
 | |
| 
 | |
|             # change the state
 | |
|             iocb.ioState = PENDING
 | |
| 
 | |
|             # let derived class figure out how to process this
 | |
|             self.process_io(iocb)
 | |
|         except:
 | |
|             # extract the error
 | |
|             err = sys.exc_info()[1]
 | |
| 
 | |
|         # if there was an error, abort the request
 | |
|         if err:
 | |
|             self.abort_io(iocb, err)
 | |
| 
 | |
|     def process_io(self, iocb):
 | |
|         """Figure out how to respond to this request.  This must be
 | |
|         provided by the derived class."""
 | |
|         raise NotImplementedError, "IOController must implement process_io()"
 | |
| 
 | |
|     def active_io(self, iocb):
 | |
|         """Called by a handler to notify the controller that a request is
 | |
|         being processed."""
 | |
|         if _debug: IOController._debug("active_io %r", iocb)
 | |
| 
 | |
|         # requests should be idle or pending before coming active
 | |
|         if (iocb.ioState != IDLE) and (iocb.ioState != PENDING):
 | |
|             raise RuntimeError, "invalid state transition (currently %d)" % (iocb.ioState,)
 | |
| 
 | |
|         # change the state
 | |
|         iocb.ioState = ACTIVE
 | |
| 
 | |
|     def complete_io(self, iocb, msg):
 | |
|         """Called by a handler to return data to the client."""
 | |
|         if _debug: IOController._debug("complete_io %r %r", iocb, msg)
 | |
| 
 | |
|         # if it completed, leave it alone
 | |
|         if iocb.ioState == COMPLETED:
 | |
|             pass
 | |
| 
 | |
|         # if it already aborted, leave it alone
 | |
|         elif iocb.ioState == ABORTED:
 | |
|             pass
 | |
| 
 | |
|         else:
 | |
|             # change the state
 | |
|             iocb.ioState = COMPLETED
 | |
|             iocb.ioResponse = msg
 | |
| 
 | |
|             # notify the client
 | |
|             iocb.trigger()
 | |
| 
 | |
|     def abort_io(self, iocb, err):
 | |
|         """Called by a handler or a client to abort a transaction."""
 | |
|         if _debug: IOController._debug("abort_io %r %r", iocb, err)
 | |
| 
 | |
|         # if it completed, leave it alone
 | |
|         if iocb.ioState == COMPLETED:
 | |
|             pass
 | |
| 
 | |
|         # if it already aborted, leave it alone
 | |
|         elif iocb.ioState == ABORTED:
 | |
|             pass
 | |
| 
 | |
|         else:
 | |
|             # change the state
 | |
|             iocb.ioState = ABORTED
 | |
|             iocb.ioError = err
 | |
| 
 | |
|             # notify the client
 | |
|             iocb.trigger()
 | |
| 
 | |
| #
 | |
| #   IOQController
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class IOQController(IOController):
 | |
| 
 | |
|     wait_time = 0.0
 | |
| 
 | |
|     def __init__(self, name=None):
 | |
|         """Initialize a queue controller."""
 | |
|         if _debug: IOQController._debug("__init__ name=%r", name)
 | |
| 
 | |
|         # give ourselves a nice name
 | |
|         if not name:
 | |
|             name = self.__class__.__name__
 | |
|         IOController.__init__(self, name)
 | |
| 
 | |
|         # start idle
 | |
|         self.state = CTRL_IDLE
 | |
| 
 | |
|         # no active iocb
 | |
|         self.active_iocb = None
 | |
| 
 | |
|         # create an IOQueue for iocb's requested when not idle
 | |
|         self.ioQueue = IOQueue(str(name) + "/Queue")
 | |
| 
 | |
|     def abort(self, err):
 | |
|         """Abort all pending requests."""
 | |
|         if _debug: IOQController._debug("abort %r", err)
 | |
| 
 | |
|         if (self.state == CTRL_IDLE):
 | |
|             if _debug: IOQController._debug("    - idle")
 | |
|             return
 | |
| 
 | |
|         while True:
 | |
|             iocb = self.ioQueue.get()
 | |
|             if not iocb:
 | |
|                 break
 | |
|             if _debug: IOQController._debug("    - iocb: %r", iocb)
 | |
| 
 | |
|             # change the state
 | |
|             iocb.ioState = ABORTED
 | |
|             iocb.ioError = err
 | |
| 
 | |
|             # notify the client
 | |
|             iocb.trigger()
 | |
| 
 | |
|         if (self.state != CTRL_IDLE):
 | |
|             if _debug: IOQController._debug("    - busy after aborts")
 | |
| 
 | |
|     def request_io(self, iocb):
 | |
|         """Called by a client to start processing a request."""
 | |
|         if _debug: IOQController._debug("request_io %r", iocb)
 | |
| 
 | |
|         # bind the iocb to this controller
 | |
|         iocb.ioController = self
 | |
| 
 | |
|         # if we're busy, queue it
 | |
|         if (self.state != CTRL_IDLE):
 | |
|             if _debug: IOQController._debug("    - busy, request queued")
 | |
| 
 | |
|             iocb.ioState = PENDING
 | |
|             self.ioQueue.put(iocb)
 | |
|             return
 | |
| 
 | |
|         try:
 | |
|             # hopefully there won't be an error
 | |
|             err = None
 | |
| 
 | |
|             # let derived class figure out how to process this
 | |
|             self.process_io(iocb)
 | |
|         except:
 | |
|             # extract the error
 | |
|             err = sys.exc_info()[1]
 | |
| 
 | |
|         # if there was an error, abort the request
 | |
|         if err:
 | |
|             self.abort_io(iocb, err)
 | |
| 
 | |
|     def process_io(self, iocb):
 | |
|         """Figure out how to respond to this request.  This must be
 | |
|         provided by the derived class."""
 | |
|         raise NotImplementedError, "IOController must implement process_io()"
 | |
| 
 | |
|     def active_io(self, iocb):
 | |
|         """Called by a handler to notify the controller that a request is
 | |
|         being processed."""
 | |
|         if _debug: IOQController._debug("active_io %r", iocb)
 | |
| 
 | |
|         # base class work first, setting iocb state and timer data
 | |
|         IOController.active_io(self, iocb)
 | |
| 
 | |
|         # change our state
 | |
|         self.state = CTRL_ACTIVE
 | |
| 
 | |
|         # keep track of the iocb
 | |
|         self.active_iocb = iocb
 | |
| 
 | |
|     def complete_io(self, iocb, msg):
 | |
|         """Called by a handler to return data to the client."""
 | |
|         if _debug: IOQController._debug("complete_io %r %r", iocb, msg)
 | |
| 
 | |
|         # check to see if it is completing the active one
 | |
|         if iocb is not self.active_iocb:
 | |
|             raise RuntimeError, "not the current iocb"
 | |
| 
 | |
|         # normal completion
 | |
|         IOController.complete_io(self, iocb, msg)
 | |
| 
 | |
|         # no longer an active iocb
 | |
|         self.active_iocb = None
 | |
| 
 | |
|         # check to see if we should wait a bit
 | |
|         if self.wait_time:
 | |
|             # change our state
 | |
|             self.state = CTRL_WAITING
 | |
| 
 | |
|             # schedule a call in the future
 | |
|             task = FunctionTask(IOQController._wait_trigger, self)
 | |
|             task.install_task(_time() + self.wait_time)
 | |
| 
 | |
|         else:
 | |
|             # change our state
 | |
|             self.state = CTRL_IDLE
 | |
| 
 | |
|             # look for more to do
 | |
|             deferred(IOQController._trigger, self)
 | |
| 
 | |
|     def abort_io(self, iocb, err):
 | |
|         """Called by a handler or a client to abort a transaction."""
 | |
|         if _debug: IOQController._debug("abort_io %r %r", iocb, err)
 | |
| 
 | |
|         # normal abort
 | |
|         IOController.abort_io(self, iocb, err)
 | |
| 
 | |
|         # check to see if it is completing the active one
 | |
|         if iocb is not self.active_iocb:
 | |
|             if _debug: IOQController._debug("    - not current iocb")
 | |
|             return
 | |
| 
 | |
|         # no longer an active iocb
 | |
|         self.active_iocb = None
 | |
| 
 | |
|         # change our state
 | |
|         self.state = CTRL_IDLE
 | |
| 
 | |
|         # look for more to do
 | |
|         deferred(IOQController._trigger, self)
 | |
| 
 | |
|     def _trigger(self):
 | |
|         """Called to launch the next request in the queue."""
 | |
|         if _debug: IOQController._debug("_trigger")
 | |
| 
 | |
|         # if we are busy, do nothing
 | |
|         if self.state != CTRL_IDLE:
 | |
|             if _debug: IOQController._debug("    - not idle")
 | |
|             return
 | |
| 
 | |
|         # if there is nothing to do, return
 | |
|         if not self.ioQueue.queue:
 | |
|             if _debug: IOQController._debug("    - empty queue")
 | |
|             return
 | |
| 
 | |
|         # get the next iocb
 | |
|         iocb = self.ioQueue.get()
 | |
| 
 | |
|         try:
 | |
|             # hopefully there won't be an error
 | |
|             err = None
 | |
| 
 | |
|             # let derived class figure out how to process this
 | |
|             self.process_io(iocb)
 | |
|         except:
 | |
|             # extract the error
 | |
|             err = sys.exc_info()[1]
 | |
| 
 | |
|         # if there was an error, abort the request
 | |
|         if err:
 | |
|             self.abort_io(iocb, err)
 | |
| 
 | |
|         # if we're idle, call again
 | |
|         if self.state == CTRL_IDLE:
 | |
|             deferred(IOQController._trigger, self)
 | |
| 
 | |
|     def _wait_trigger(self):
 | |
|         """Called to launch the next request in the queue."""
 | |
|         if _debug: IOQController._debug("_wait_trigger")
 | |
| 
 | |
|         # make sure we are waiting
 | |
|         if (self.state != CTRL_WAITING):
 | |
|             raise RuntimeError, "not waiting"
 | |
| 
 | |
|         # change our state
 | |
|         self.state = CTRL_IDLE
 | |
| 
 | |
|         # look for more to do
 | |
|         IOQController._trigger(self)
 | |
| 
 | |
| #
 | |
| #   IOProxy
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class IOProxy:
 | |
| 
 | |
|     def __init__(self, controllerName, serverName=None, requestLimit=None):
 | |
|         """Create an IO client.  It implements request_io like a controller, but
 | |
|         passes requests on to a local controller if it happens to be in the 
 | |
|         same process, or the IOProxyServer instance to forward on for processing."""
 | |
|         if _debug: IOProxy._debug("__init__ %r serverName=%r, requestLimit=%r", controllerName, serverName, requestLimit)
 | |
| 
 | |
|         # save the server reference
 | |
|         self.ioControllerRef = controllerName
 | |
|         self.ioServerRef = serverName
 | |
| 
 | |
|         # set a limit on how many requests can be submitted
 | |
|         self.ioRequestLimit = requestLimit
 | |
|         self.ioPending = set()
 | |
|         self.ioBlocked = deque()
 | |
| 
 | |
|         # bind to a local controller if possible
 | |
|         if not serverName:
 | |
|             self.ioBind = _local_controllers.get(controllerName, None)
 | |
|             if self.ioBind:
 | |
|                 if _debug: IOProxy._debug("    - local bind successful")
 | |
|             else:
 | |
|                 if _debug: IOProxy._debug("    - local bind deferred")
 | |
|         else:
 | |
|             self.ioBind = None
 | |
|             if _debug: IOProxy._debug("    - bind deferred")
 | |
| 
 | |
|     def request_io(self, iocb, urgent=False):
 | |
|         """Called by a client to start processing a request."""
 | |
|         if _debug: IOProxy._debug("request_io %r urgent=%r", iocb, urgent)
 | |
|         global _proxy_server
 | |
| 
 | |
|         # save the server and controller reference
 | |
|         iocb.ioServerRef = self.ioServerRef
 | |
|         iocb.ioControllerRef = self.ioControllerRef
 | |
| 
 | |
|         # check to see if it needs binding
 | |
|         if not self.ioBind:
 | |
|             # if the server is us, look for a local controller
 | |
|             if not self.ioServerRef:
 | |
|                 self.ioBind = _local_controllers.get(self.ioControllerRef, None)
 | |
|                 if not self.ioBind:
 | |
|                     iocb.abort("no local controller %s" % (self.ioControllerRef,))
 | |
|                     return
 | |
|                 if _debug: IOProxy._debug("    - local bind successful")
 | |
|             else:
 | |
|                 if not _proxy_server:
 | |
|                     _proxy_server = IOProxyServer()
 | |
| 
 | |
|                 self.ioBind = _proxy_server
 | |
|                 if _debug: IOProxy._debug("    - proxy bind successful: %r", self.ioBind)
 | |
| 
 | |
|         # if this isn't urgent and there is a limit, see if we've reached it
 | |
|         if (not urgent) and self.ioRequestLimit:
 | |
|             # call back when this is completed
 | |
|             iocb.add_callback(self._proxy_trigger)
 | |
| 
 | |
|             # check for the limit
 | |
|             if len(self.ioPending) < self.ioRequestLimit:
 | |
|                 if _debug: IOProxy._debug("    - cleared for launch")
 | |
| 
 | |
|                 self.ioPending.add(iocb)
 | |
|                 self.ioBind.request_io(iocb)
 | |
|             else:
 | |
|                 # save it for later
 | |
|                 if _debug: IOProxy._debug("    - save for later")
 | |
| 
 | |
|                 self.ioBlocked.append(iocb)
 | |
|         else:
 | |
|             # just pass it along
 | |
|             self.ioBind.request_io(iocb)
 | |
| 
 | |
|     def _proxy_trigger(self, iocb):
 | |
|         """This has completed, remove it from the set of pending requests 
 | |
|         and see if it's OK to start up the next one."""
 | |
|         if _debug: IOProxy._debug("_proxy_trigger %r", iocb)
 | |
| 
 | |
|         if iocb not in self.ioPending:
 | |
|             if _debug: IOProxy._warning("iocb not pending: %r", iocb)
 | |
|         else:
 | |
|             self.ioPending.remove(iocb)
 | |
| 
 | |
|             # check to send another one
 | |
|             if (len(self.ioPending) < self.ioRequestLimit) and self.ioBlocked:
 | |
|                 nextio = self.ioBlocked.popleft()
 | |
|                 if _debug: IOProxy._debug("    - cleared for launch: %r", nextio)
 | |
| 
 | |
|                 # this one is now pending
 | |
|                 self.ioPending.add(nextio)
 | |
|                 self.ioBind.request_io(nextio)
 | |
| 
 | |
| #
 | |
| #   IOServer
 | |
| #
 | |
| 
 | |
| PORT = 8002
 | |
| SERVER_TIMEOUT = 60
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class IOServer(IOController, Client):
 | |
| 
 | |
|     def __init__(self, addr=('',PORT)):
 | |
|         """Initialize the remote IO handler."""
 | |
|         if _debug: IOServer._debug("__init__ %r", addr)
 | |
|         IOController.__init__(self)
 | |
| 
 | |
|         # create a UDP director
 | |
|         self.server = UDPDirector(addr)
 | |
|         bind(self, self.server)
 | |
| 
 | |
|         # dictionary of IOCBs as a server
 | |
|         self.remoteIOCB = {}
 | |
| 
 | |
|     def confirmation(self, pdu):
 | |
|         if _debug: IOServer._debug('confirmation %r', pdu)
 | |
| 
 | |
|         addr = pdu.pduSource
 | |
|         request = pdu.pduData
 | |
| 
 | |
|         try:
 | |
|             # parse the request
 | |
|             request = cPickle.loads(request)
 | |
|             if _debug: _commlog.debug(">>> %s: S %s %r" % (_strftime(), str(addr), request))
 | |
| 
 | |
|             # pick the message
 | |
|             if (request[0] == 0):
 | |
|                 self.new_iocb(addr, *request[1:])
 | |
|             elif (request[0] == 1):
 | |
|                 self.complete_iocb(addr, *request[1:])
 | |
|             elif (request[0] == 2):
 | |
|                 self.abort_iocb(addr, *request[1:])
 | |
|         except:
 | |
|             # extract the error
 | |
|             err = sys.exc_info()[1]
 | |
|             IOServer._exception("error %r processing %r from %r", err, request, addr)
 | |
| 
 | |
|     def callback(self, iocb):
 | |
|         """Callback when an iocb is completed by a local controller and the
 | |
|         result needs to be sent back to the client."""
 | |
|         if _debug: IOServer._debug("callback %r", iocb)
 | |
| 
 | |
|         # make sure it's one of ours
 | |
|         if not self.remoteIOCB.has_key(iocb):
 | |
|             IOServer._warning("IOCB not owned by server: %r", iocb)
 | |
|             return
 | |
| 
 | |
|         # get the client information
 | |
|         clientID, clientAddr = self.remoteIOCB[iocb]
 | |
| 
 | |
|         # we're done with this
 | |
|         del self.remoteIOCB[iocb]
 | |
| 
 | |
|         # build a response
 | |
|         if iocb.ioState == COMPLETED:
 | |
|             response = (1, clientID, iocb.ioResponse)
 | |
|         elif iocb.ioState == ABORTED:
 | |
|             response = (2, clientID, iocb.ioError)
 | |
|         else:
 | |
|             raise RuntimeError, "IOCB invalid state"
 | |
| 
 | |
|         if _debug: _commlog.debug("<<< %s: S %s %r" % (_strftime(), clientAddr, response))
 | |
| 
 | |
|         response = cPickle.dumps( response, 1 )
 | |
| 
 | |
|         # send it to the client
 | |
|         self.request(PDU(response, destination=clientAddr))
 | |
| 
 | |
|     def abort(self, err):
 | |
|         """Called by a local application to abort all transactions."""
 | |
|         if _debug: IOServer._debug("abort %r", err)
 | |
| 
 | |
|         for iocb in self.remoteIOCB.keys():
 | |
|             self.abort_io(iocb, err)
 | |
| 
 | |
|     def abort_io(self, iocb, err):
 | |
|         """Called by a local client or a local controlled to abort a transaction."""
 | |
|         if _debug: IOServer._debug("abort_io %r %r", iocb, err)
 | |
| 
 | |
|         # if it completed, leave it alone
 | |
|         if iocb.ioState == COMPLETED:
 | |
|             pass
 | |
| 
 | |
|         # if it already aborted, leave it alone
 | |
|         elif iocb.ioState == ABORTED:
 | |
|             pass
 | |
| 
 | |
|         elif self.remoteIOCB.has_key(iocb):
 | |
|             # get the client information
 | |
|             clientID, clientAddr = self.remoteIOCB[iocb]
 | |
| 
 | |
|             # we're done with this
 | |
|             del self.remoteIOCB[iocb]
 | |
| 
 | |
|             # build an abort response
 | |
|             response = (2, clientID, err)
 | |
|             if _debug: _commlog.debug("<<< %s: S %s %r" % (_strftime(), clientAddr, response))
 | |
| 
 | |
|             response = cPickle.dumps( response, 1 )
 | |
| 
 | |
|             # send it to the client
 | |
|             self.socket.sendto( response, clientAddr )
 | |
| 
 | |
|         else:
 | |
|             IOServer._error("no reference to aborting iocb: %r", iocb)
 | |
| 
 | |
|         # change the state
 | |
|         iocb.ioState = ABORTED
 | |
|         iocb.ioError = err
 | |
| 
 | |
|         # notify the client
 | |
|         iocb.trigger()
 | |
| 
 | |
|     def new_iocb(self, clientAddr, iocbid, controllerName, args, kwargs):
 | |
|         """Called when the server receives a new request."""
 | |
|         if _debug: IOServer._debug("new_iocb %r %r %r %r %r", clientAddr, iocbid, controllerName, args, kwargs)
 | |
| 
 | |
|         # look for a controller
 | |
|         controller = _local_controllers.get(controllerName, None)
 | |
|         if not controller:
 | |
|             # create a nice error message
 | |
|             err = RuntimeError("no local controller '%s'" % (controllerName, ))
 | |
| 
 | |
|             # build an abort response
 | |
|             response = (2, iocbid, err)
 | |
|             if _debug: _commlog.debug("<<< %s: S %s %r" % (_strftime(), clientAddr, response))
 | |
| 
 | |
|             response = cPickle.dumps( response, 1 )
 | |
| 
 | |
|             # send it to the server
 | |
|             self.request(PDU(response, destination=clientAddr))
 | |
| 
 | |
|         else:
 | |
|             # create an IOCB
 | |
|             iocb = IOCB(*args, **kwargs)
 | |
|             if _debug: IOServer._debug("    - local IOCB %r bound to remote %r", iocb.ioID, iocbid)
 | |
| 
 | |
|             # save a reference to it
 | |
|             self.remoteIOCB[iocb] = (iocbid, clientAddr)
 | |
| 
 | |
|             # make sure we're notified when it completes
 | |
|             iocb.add_callback(self.callback)
 | |
| 
 | |
|             # pass it along
 | |
|             controller.request_io(iocb)
 | |
| 
 | |
|     def abort_iocb(self, addr, iocbid, err):
 | |
|         """Called when the client or server receives an abort request."""
 | |
|         if _debug: IOServer._debug("abort_iocb %r %r %r", addr, iocbid, err)
 | |
| 
 | |
|         # see if this came from a client
 | |
|         for iocb in self.remoteIOCB.keys():
 | |
|             clientID, clientAddr = self.remoteIOCB[iocb]
 | |
|             if (addr == clientAddr) and (clientID == iocbid):
 | |
|                 break
 | |
|         else:
 | |
|             IOServer._error("no reference to aborting iocb %r from %r", iocbid, addr)
 | |
|             return
 | |
|         if _debug: IOServer._debug("    - local IOCB %r bound to remote %r", iocb.ioID, iocbid)
 | |
| 
 | |
|         # we're done with this
 | |
|         del self.remoteIOCB[iocb]
 | |
| 
 | |
|         # clear the callback, we already know
 | |
|         iocb.ioCallback = []
 | |
| 
 | |
|         # tell the local controller about the abort
 | |
|         iocb.abort(err)
 | |
| 
 | |
| #
 | |
| #   IOProxyServer
 | |
| #
 | |
| 
 | |
| SERVER_TIMEOUT = 60
 | |
| 
 | |
| @bacpypes_debugging
 | |
| class IOProxyServer(IOController, Client):
 | |
| 
 | |
|     def __init__(self, addr=('', 0), name=None):
 | |
|         """Initialize the remote IO handler."""
 | |
|         if _debug: IOProxyServer._debug("__init__")
 | |
|         IOController.__init__(self, name=name)
 | |
| 
 | |
|         # create a UDP director
 | |
|         self.server = UDPDirector(addr)
 | |
|         bind(self, self.server)
 | |
|         if _debug: IOProxyServer._debug("    - bound to %r", self.server.socket.getsockname())
 | |
| 
 | |
|         # dictionary of IOCBs as a client
 | |
|         self.localIOCB = {}
 | |
| 
 | |
|     def confirmation(self, pdu):
 | |
|         if _debug: IOProxyServer._debug('confirmation %r', pdu)
 | |
| 
 | |
|         addr = pdu.pduSource
 | |
|         request = pdu.pduData
 | |
| 
 | |
|         try:
 | |
|             # parse the request
 | |
|             request = cPickle.loads(request)
 | |
|             if _debug: _commlog.debug(">>> %s: P %s %r" % (_strftime(), addr, request))
 | |
| 
 | |
|             # pick the message
 | |
|             if (request[0] == 1):
 | |
|                 self.complete_iocb(addr, *request[1:])
 | |
|             elif (request[0] == 2):
 | |
|                 self.abort_iocb(addr, *request[1:])
 | |
|         except:
 | |
|             # extract the error
 | |
|             err = sys.exc_info()[1]
 | |
|             IOProxyServer._exception("error %r processing %r from %r", err, request, addr)
 | |
| 
 | |
|     def process_io(self, iocb):
 | |
|         """Package up the local IO request and send it to the server."""
 | |
|         if _debug: IOProxyServer._debug("process_io %r", iocb)
 | |
| 
 | |
|         # save a reference in our dictionary
 | |
|         self.localIOCB[iocb.ioID] = iocb
 | |
| 
 | |
|         # start a default timer if one hasn't already been set
 | |
|         if not iocb.ioTimeout:
 | |
|             iocb.set_timeout( SERVER_TIMEOUT, RuntimeError("no response from " + iocb.ioServerRef))
 | |
| 
 | |
|         # build a message
 | |
|         request = (0, iocb.ioID, iocb.ioControllerRef, iocb.args, iocb.kwargs)
 | |
|         if _debug: _commlog.debug("<<< %s: P %s %r" % (_strftime(), iocb.ioServerRef, request))
 | |
| 
 | |
|         request = cPickle.dumps( request, 1 )
 | |
| 
 | |
|         # send it to the server
 | |
|         self.request(PDU(request, destination=(iocb.ioServerRef, PORT)))
 | |
| 
 | |
|     def abort(self, err):
 | |
|         """Called by a local application to abort all transactions, local
 | |
|         and remote."""
 | |
|         if _debug: IOProxyServer._debug("abort %r", err)
 | |
| 
 | |
|         for iocb in self.localIOCB.values():
 | |
|             self.abort_io(iocb, err)
 | |
| 
 | |
|     def abort_io(self, iocb, err):
 | |
|         """Called by a local client or a local controlled to abort a transaction."""
 | |
|         if _debug: IOProxyServer._debug("abort_io %r %r", iocb, err)
 | |
| 
 | |
|         # if it completed, leave it alone
 | |
|         if iocb.ioState == COMPLETED:
 | |
|             pass
 | |
| 
 | |
|         # if it already aborted, leave it alone
 | |
|         elif iocb.ioState == ABORTED:
 | |
|             pass
 | |
| 
 | |
|         elif self.localIOCB.has_key(iocb.ioID):
 | |
|             # delete the dictionary reference
 | |
|             del self.localIOCB[iocb.ioID]
 | |
| 
 | |
|             # build an abort request
 | |
|             request = (2, iocb.ioID, err)
 | |
|             if _debug: _commlog.debug("<<< %s: P %s %r" % (_strftime(), iocb.ioServerRef, request))
 | |
| 
 | |
|             request = cPickle.dumps( request, 1 )
 | |
| 
 | |
|             # send it to the server
 | |
|             self.request(PDU(request, destination=(iocb.ioServerRef, PORT)))
 | |
| 
 | |
|         else:
 | |
|             raise RuntimeError, "no reference to aborting iocb: %r" % (iocb.ioID,)
 | |
| 
 | |
|         # change the state
 | |
|         iocb.ioState = ABORTED
 | |
|         iocb.ioError = err
 | |
| 
 | |
|         # notify the client
 | |
|         iocb.trigger()
 | |
| 
 | |
|     def complete_iocb(self, serverAddr, iocbid, msg):
 | |
|         """Called when the client receives a response to a request."""
 | |
|         if _debug: IOProxyServer._debug("complete_iocb %r %r %r", serverAddr, iocbid, msg)
 | |
| 
 | |
|         # assume nothing
 | |
|         iocb = None
 | |
| 
 | |
|         # make sure this is a local request
 | |
|         if not self.localIOCB.has_key(iocbid):
 | |
|             IOProxyServer._error("no reference to IOCB %r", iocbid)
 | |
|             if _debug: IOProxyServer._debug("    - localIOCB: %r", self.localIOCB)
 | |
|         else:
 | |
|             # get the iocb
 | |
|             iocb = self.localIOCB[iocbid]
 | |
| 
 | |
|             # delete the dictionary reference
 | |
|             del self.localIOCB[iocbid]
 | |
| 
 | |
|         if iocb:
 | |
|             # change the state
 | |
|             iocb.ioState = COMPLETED
 | |
|             iocb.ioResponse = msg
 | |
| 
 | |
|             # notify the client
 | |
|             iocb.trigger()
 | |
| 
 | |
|     def abort_iocb(self, addr, iocbid, err):
 | |
|         """Called when the client or server receives an abort request."""
 | |
|         if _debug: IOProxyServer._debug("abort_iocb %r %r %r", addr, iocbid, err)
 | |
| 
 | |
|         if not self.localIOCB.has_key(iocbid):
 | |
|             raise RuntimeError, "no reference to aborting iocb: %r" % (iocbid,)
 | |
| 
 | |
|         # get the iocb
 | |
|         iocb = self.localIOCB[iocbid]
 | |
| 
 | |
|         # delete the dictionary reference
 | |
|         del self.localIOCB[iocbid]
 | |
| 
 | |
|         # change the state
 | |
|         iocb.ioState = ABORTED
 | |
|         iocb.ioError = err
 | |
| 
 | |
|         # notify the client
 | |
|         iocb.trigger()
 | |
| 
 | |
| #
 | |
| #   abort
 | |
| #
 | |
| 
 | |
| @bacpypes_debugging
 | |
| def abort(err):
 | |
|     """Abort everything, everywhere."""
 | |
|     if _debug: abort._debug("abort %r", err)
 | |
| 
 | |
|     # start with the server
 | |
|     if IOServer._highlander:
 | |
|         IOServer._highlander.abort(err)
 | |
| 
 | |
|     # now do everything local
 | |
|     for controller in _local_controllers.values():
 | |
|         controller.abort(err)
 | 
