123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- """Multiple-producer-multiple-consumer signal-dispatching
- dispatcher is the core of the PyDispatcher system,
- providing the primary API and the core logic for the
- system.
- Module attributes of note:
- Any -- Singleton used to signal either "Any Sender" or
- "Any Signal". See documentation of the _Any class.
- Anonymous -- Singleton used to signal "Anonymous Sender"
- See documentation of the _Anonymous class.
- Internal attributes:
- WEAKREF_TYPES -- tuple of types/classes which represent
- weak references to receivers, and thus must be de-
- referenced on retrieval to retrieve the callable
- object
- connections -- { senderkey (id) : { signal : [receivers...]}}
- senders -- { senderkey (id) : weakref(sender) }
- used for cleaning up sender references on sender
- deletion
- sendersBack -- { receiverkey (id) : [senderkey (id)...] }
- used for cleaning up receiver references on receiver
- deletion, (considerably speeds up the cleanup process
- vs. the original code.)
- """
- from __future__ import generators
- import weakref
- from grass.pydispatch import saferef, robustapply, errors
- __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
- __cvsid__ = "Id: dispatcher.py,v 1.1 2010/03/30 15:45:55 mcfletch Exp"
- __version__ = "Revision: 1.1"
- class _Parameter:
- """Used to represent default parameter values."""
- def __repr__(self):
- return self.__class__.__name__
- class _Any(_Parameter):
- """Singleton used to signal either "Any Sender" or "Any Signal"
- The Any object can be used with connect, disconnect,
- send, or sendExact to signal that the parameter given
- Any should react to all senders/signals, not just
- a particular sender/signal.
- """
- Any = _Any()
- class _Anonymous(_Parameter):
- """Singleton used to signal "Anonymous Sender"
- The Anonymous object is used to signal that the sender
- of a message is not specified (as distinct from being
- "any sender"). Registering callbacks for Anonymous
- will only receive messages sent without senders. Sending
- with anonymous will only send messages to those receivers
- registered for Any or Anonymous.
- Note:
- The default sender for connect is Any, while the
- default sender for send is Anonymous. This has
- the effect that if you do not specify any senders
- in either function then all messages are routed
- as though there was a single sender (Anonymous)
- being used everywhere.
- """
- Anonymous = _Anonymous()
- WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
- connections = {}
- senders = {}
- sendersBack = {}
- def connect(receiver, signal=Any, sender=Any, weak=True):
- """Connect receiver to sender for signal
- receiver -- a callable Python object which is to receive
- messages/signals/events. Receivers must be hashable
- objects.
- if weak is True, then receiver must be weak-referencable
- (more precisely saferef.safeRef() must be able to create
- a reference to the receiver).
- Receivers are fairly flexible in their specification,
- as the machinery in the robustApply module takes care
- of most of the details regarding figuring out appropriate
- subsets of the sent arguments to apply to a given
- receiver.
- Note:
- if receiver is itself a weak reference (a callable),
- it will be de-referenced by the system's machinery,
- so *generally* weak references are not suitable as
- receivers, though some use might be found for the
- facility whereby a higher-level library passes in
- pre-weakrefed receiver references.
- signal -- the signal to which the receiver should respond
- if Any, receiver will receive any signal from the
- indicated sender (which might also be Any, but is not
- necessarily Any).
- Otherwise must be a hashable Python object other than
- None (DispatcherError raised on None).
- sender -- the sender to which the receiver should respond
- if Any, receiver will receive the indicated signals
- from any sender.
- if Anonymous, receiver will only receive indicated
- signals from send/sendExact which do not specify a
- sender, or specify Anonymous explicitly as the sender.
- Otherwise can be any python object.
- weak -- whether to use weak references to the receiver
- By default, the module will attempt to use weak
- references to the receiver objects. If this parameter
- is false, then strong references will be used.
- returns None, may raise DispatcherTypeError
- """
- if signal is None:
- raise errors.DispatcherTypeError(
- "Signal cannot be None (receiver=%r sender=%r)" % (receiver, sender)
- )
- if weak:
- receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
- senderkey = id(sender)
- if senderkey in connections:
- signals = connections[senderkey]
- else:
- connections[senderkey] = signals = {}
- # Keep track of senders for cleanup.
- # Is Anonymous something we want to clean up?
- if sender not in (None, Anonymous, Any):
- def remove(object, senderkey=senderkey):
- _removeSender(senderkey=senderkey)
- # Skip objects that can not be weakly referenced, which means
- # they won't be automatically cleaned up, but that's too bad.
- try:
- weakSender = weakref.ref(sender, remove)
- senders[senderkey] = weakSender
- except Exception:
- pass
- receiverID = id(receiver)
- # get current set, remove any current references to
- # this receiver in the set, including back-references
- if signal in signals:
- receivers = signals[signal]
- _removeOldBackRefs(senderkey, signal, receiver, receivers)
- else:
- receivers = signals[signal] = []
- try:
- current = sendersBack.get(receiverID)
- if current is None:
- sendersBack[receiverID] = current = []
- if senderkey not in current:
- current.append(senderkey)
- except Exception:
- pass
- receivers.append(receiver)
- def disconnect(receiver, signal=Any, sender=Any, weak=True):
- """Disconnect receiver from sender for signal
- receiver -- the registered receiver to disconnect
- signal -- the registered signal to disconnect
- sender -- the registered sender to disconnect
- weak -- the weakref state to disconnect
- disconnect reverses the process of connect,
- the semantics for the individual elements are
- logically equivalent to a tuple of
- (receiver, signal, sender, weak) used as a key
- to be deleted from the internal routing tables.
- (The actual process is slightly more complex
- but the semantics are basically the same).
- Note:
- Using disconnect is not required to cleanup
- routing when an object is deleted, the framework
- will remove routes for deleted objects
- automatically. It's only necessary to disconnect
- if you want to stop routing to a live object.
- returns None, may raise DispatcherTypeError or
- DispatcherKeyError
- """
- if signal is None:
- raise errors.DispatcherTypeError(
- "Signal cannot be None (receiver=%r sender=%r)" % (receiver, sender)
- )
- if weak:
- receiver = saferef.safeRef(receiver)
- senderkey = id(sender)
- try:
- signals = connections[senderkey]
- receivers = signals[signal]
- except KeyError:
- raise errors.DispatcherKeyError(
- """No receivers found for signal %r from sender %r""" % (signal, sender)
- )
- try:
- # also removes from receivers
- _removeOldBackRefs(senderkey, signal, receiver, receivers)
- except ValueError:
- raise errors.DispatcherKeyError(
- """No connection to receiver %s for signal %s from sender %s"""
- % (receiver, signal, sender)
- )
- _cleanupConnections(senderkey, signal)
- def getReceivers(sender=Any, signal=Any):
- """Get list of receivers from global tables
- This utility function allows you to retrieve the
- raw list of receivers from the connections table
- for the given sender and signal pair.
- Note:
- there is no guarantee that this is the actual list
- stored in the connections table, so the value
- should be treated as a simple iterable/truth value
- rather than, for instance a list to which you
- might append new records.
- Normally you would use liveReceivers(getReceivers(...))
- to retrieve the actual receiver objects as an iterable
- object.
- """
- try:
- return connections[id(sender)][signal]
- except KeyError:
- return []
- def liveReceivers(receivers):
- """Filter sequence of receivers to get resolved, live receivers
- This is a generator which will iterate over
- the passed sequence, checking for weak references
- and resolving them, then returning all live
- receivers.
- """
- for receiver in receivers:
- if isinstance(receiver, WEAKREF_TYPES):
- # Dereference the weak reference.
- receiver = receiver()
- if receiver is not None:
- yield receiver
- else:
- yield receiver
- def getAllReceivers(sender=Any, signal=Any):
- """Get list of all receivers from global tables
- This gets all receivers which should receive
- the given signal from sender, each receiver should
- be produced only once by the resulting generator
- """
- receivers = {}
- for set in (
- # Get receivers that receive *this* signal from *this* sender.
- getReceivers(sender, signal),
- # Add receivers that receive *any* signal from *this* sender.
- getReceivers(sender, Any),
- # Add receivers that receive *this* signal from *any* sender.
- getReceivers(Any, signal),
- # Add receivers that receive *any* signal from *any* sender.
- getReceivers(Any, Any),
- ):
- for receiver in set:
- if receiver: # filter out dead instance-method weakrefs
- try:
- if receiver not in receivers:
- receivers[receiver] = 1
- yield receiver
- except TypeError:
- # dead weakrefs raise TypeError on hash...
- pass
- def send(signal=Any, sender=Anonymous, *arguments, **named):
- """Send signal from sender to all connected receivers.
- signal -- (hashable) signal value, see connect for details
- sender -- the sender of the signal
- if Any, only receivers registered for Any will receive
- the message.
- if Anonymous, only receivers registered to receive
- messages from Anonymous or Any will receive the message
- Otherwise can be any python object (normally one
- registered with a connect if you actually want
- something to occur).
- arguments -- positional arguments which will be passed to
- *all* receivers. Note that this may raise TypeErrors
- if the receivers do not allow the particular arguments.
- Note also that arguments are applied before named
- arguments, so they should be used with care.
- named -- named arguments which will be filtered according
- to the parameters of the receivers to only provide those
- acceptable to the receiver.
- Return a list of tuple pairs [(receiver, response), ... ]
- if any receiver raises an error, the error propagates back
- through send, terminating the dispatch loop, so it is quite
- possible to not have all receivers called if a raises an
- error.
- """
- # Call each receiver with whatever arguments it can accept.
- # Return a list of tuple pairs [(receiver, response), ... ].
- responses = []
- for receiver in liveReceivers(getAllReceivers(sender, signal)):
- response = robustapply.robustApply(
- receiver, signal=signal, sender=sender, *arguments, **named
- )
- responses.append((receiver, response))
- return responses
- def sendExact(signal=Any, sender=Anonymous, *arguments, **named):
- """Send signal only to those receivers registered for exact message
- sendExact allows for avoiding Any/Anonymous registered
- handlers, sending only to those receivers explicitly
- registered for a particular signal on a particular
- sender.
- """
- responses = []
- for receiver in liveReceivers(getReceivers(sender, signal)):
- response = robustapply.robustApply(
- receiver, signal=signal, sender=sender, *arguments, **named
- )
- responses.append((receiver, response))
- return responses
- def _removeReceiver(receiver):
- """Remove receiver from connections."""
- if not sendersBack or not connections:
- # During module cleanup the objects will be replaced with None
- # The order of replacing many change, so both variables need
- # to be checked.
- return False
- backKey = id(receiver)
- try:
- backSet = sendersBack.pop(backKey)
- except KeyError:
- return False
- else:
- for senderkey in backSet:
- try:
- signals = list(connections[senderkey])
- except KeyError:
- pass
- else:
- for signal in signals:
- try:
- receivers = connections[senderkey][signal]
- except KeyError:
- pass
- else:
- try:
- receivers.remove(receiver)
- except Exception:
- pass
- _cleanupConnections(senderkey, signal)
- def _cleanupConnections(senderkey, signal):
- """Delete any empty signals for senderkey. Delete senderkey if empty."""
- try:
- receivers = connections[senderkey][signal]
- except Exception:
- pass
- else:
- if not receivers:
- # No more connected receivers. Therefore, remove the signal.
- try:
- signals = connections[senderkey]
- except KeyError:
- pass
- else:
- del signals[signal]
- if not signals:
- # No more signal connections. Therefore, remove the sender.
- _removeSender(senderkey)
- def _removeSender(senderkey):
- """Remove senderkey from connections."""
- _removeBackrefs(senderkey)
- try:
- del connections[senderkey]
- except KeyError:
- pass
- # Senderkey will only be in senders dictionary if sender
- # could be weakly referenced.
- try:
- del senders[senderkey]
- except Exception:
- pass
- def _removeBackrefs(senderkey):
- """Remove all back-references to this senderkey"""
- try:
- signals = connections[senderkey]
- except KeyError:
- signals = None
- else:
- items = signals.items()
- def allReceivers():
- for signal, set in items:
- for item in set:
- yield item
- for receiver in allReceivers():
- _killBackref(receiver, senderkey)
- def _removeOldBackRefs(senderkey, signal, receiver, receivers):
- """Kill old sendersBack references from receiver
- This guards against multiple registration of the same
- receiver for a given signal and sender leaking memory
- as old back reference records build up.
- Also removes old receiver instance from receivers
- """
- try:
- index = receivers.index(receiver)
- # need to scan back references here and remove senderkey
- except ValueError:
- return False
- else:
- oldReceiver = receivers[index]
- del receivers[index]
- found = 0
- signals = connections.get(signal)
- if signals is not None:
- for sig, recs in connections.get(signal, {}).iteritems():
- if sig != signal:
- for rec in recs:
- if rec is oldReceiver:
- found = 1
- break
- if not found:
- _killBackref(oldReceiver, senderkey)
- return True
- return False
- def _killBackref(receiver, senderkey):
- """Do the actual removal of back reference from receiver to senderkey"""
- receiverkey = id(receiver)
- set = sendersBack.get(receiverkey, ())
- while senderkey in set:
- try:
- set.remove(senderkey)
- except Exception:
- break
- if not set:
- try:
- del sendersBack[receiverkey]
- except KeyError:
- pass
- return True
|