dispatcher.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. """Multiple-producer-multiple-consumer signal-dispatching
  2. dispatcher is the core of the PyDispatcher system,
  3. providing the primary API and the core logic for the
  4. system.
  5. Module attributes of note:
  6. Any -- Singleton used to signal either "Any Sender" or
  7. "Any Signal". See documentation of the _Any class.
  8. Anonymous -- Singleton used to signal "Anonymous Sender"
  9. See documentation of the _Anonymous class.
  10. Internal attributes:
  11. WEAKREF_TYPES -- tuple of types/classes which represent
  12. weak references to receivers, and thus must be de-
  13. referenced on retrieval to retrieve the callable
  14. object
  15. connections -- { senderkey (id) : { signal : [receivers...]}}
  16. senders -- { senderkey (id) : weakref(sender) }
  17. used for cleaning up sender references on sender
  18. deletion
  19. sendersBack -- { receiverkey (id) : [senderkey (id)...] }
  20. used for cleaning up receiver references on receiver
  21. deletion, (considerably speeds up the cleanup process
  22. vs. the original code.)
  23. """
  24. from __future__ import generators
  25. import weakref
  26. from grass.pydispatch import saferef, robustapply, errors
  27. __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
  28. __cvsid__ = "Id: dispatcher.py,v 1.1 2010/03/30 15:45:55 mcfletch Exp"
  29. __version__ = "Revision: 1.1"
  30. class _Parameter:
  31. """Used to represent default parameter values."""
  32. def __repr__(self):
  33. return self.__class__.__name__
  34. class _Any(_Parameter):
  35. """Singleton used to signal either "Any Sender" or "Any Signal"
  36. The Any object can be used with connect, disconnect,
  37. send, or sendExact to signal that the parameter given
  38. Any should react to all senders/signals, not just
  39. a particular sender/signal.
  40. """
  41. Any = _Any()
  42. class _Anonymous(_Parameter):
  43. """Singleton used to signal "Anonymous Sender"
  44. The Anonymous object is used to signal that the sender
  45. of a message is not specified (as distinct from being
  46. "any sender"). Registering callbacks for Anonymous
  47. will only receive messages sent without senders. Sending
  48. with anonymous will only send messages to those receivers
  49. registered for Any or Anonymous.
  50. Note:
  51. The default sender for connect is Any, while the
  52. default sender for send is Anonymous. This has
  53. the effect that if you do not specify any senders
  54. in either function then all messages are routed
  55. as though there was a single sender (Anonymous)
  56. being used everywhere.
  57. """
  58. Anonymous = _Anonymous()
  59. WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
  60. connections = {}
  61. senders = {}
  62. sendersBack = {}
  63. def connect(receiver, signal=Any, sender=Any, weak=True):
  64. """Connect receiver to sender for signal
  65. receiver -- a callable Python object which is to receive
  66. messages/signals/events. Receivers must be hashable
  67. objects.
  68. if weak is True, then receiver must be weak-referencable
  69. (more precisely saferef.safeRef() must be able to create
  70. a reference to the receiver).
  71. Receivers are fairly flexible in their specification,
  72. as the machinery in the robustApply module takes care
  73. of most of the details regarding figuring out appropriate
  74. subsets of the sent arguments to apply to a given
  75. receiver.
  76. Note:
  77. if receiver is itself a weak reference (a callable),
  78. it will be de-referenced by the system's machinery,
  79. so *generally* weak references are not suitable as
  80. receivers, though some use might be found for the
  81. facility whereby a higher-level library passes in
  82. pre-weakrefed receiver references.
  83. signal -- the signal to which the receiver should respond
  84. if Any, receiver will receive any signal from the
  85. indicated sender (which might also be Any, but is not
  86. necessarily Any).
  87. Otherwise must be a hashable Python object other than
  88. None (DispatcherError raised on None).
  89. sender -- the sender to which the receiver should respond
  90. if Any, receiver will receive the indicated signals
  91. from any sender.
  92. if Anonymous, receiver will only receive indicated
  93. signals from send/sendExact which do not specify a
  94. sender, or specify Anonymous explicitly as the sender.
  95. Otherwise can be any python object.
  96. weak -- whether to use weak references to the receiver
  97. By default, the module will attempt to use weak
  98. references to the receiver objects. If this parameter
  99. is false, then strong references will be used.
  100. returns None, may raise DispatcherTypeError
  101. """
  102. if signal is None:
  103. raise errors.DispatcherTypeError(
  104. 'Signal cannot be None (receiver=%r sender=%r)' % (receiver,
  105. sender)
  106. )
  107. if weak:
  108. receiver = saferef.safeRef(receiver, onDelete=_removeReceiver)
  109. senderkey = id(sender)
  110. if senderkey in connections:
  111. signals = connections[senderkey]
  112. else:
  113. connections[senderkey] = signals = {}
  114. # Keep track of senders for cleanup.
  115. # Is Anonymous something we want to clean up?
  116. if sender not in (None, Anonymous, Any):
  117. def remove(object, senderkey=senderkey):
  118. _removeSender(senderkey=senderkey)
  119. # Skip objects that can not be weakly referenced, which means
  120. # they won't be automatically cleaned up, but that's too bad.
  121. try:
  122. weakSender = weakref.ref(sender, remove)
  123. senders[senderkey] = weakSender
  124. except:
  125. pass
  126. receiverID = id(receiver)
  127. # get current set, remove any current references to
  128. # this receiver in the set, including back-references
  129. if signal in signals:
  130. receivers = signals[signal]
  131. _removeOldBackRefs(senderkey, signal, receiver, receivers)
  132. else:
  133. receivers = signals[signal] = []
  134. try:
  135. current = sendersBack.get(receiverID)
  136. if current is None:
  137. sendersBack[receiverID] = current = []
  138. if senderkey not in current:
  139. current.append(senderkey)
  140. except:
  141. pass
  142. receivers.append(receiver)
  143. def disconnect(receiver, signal=Any, sender=Any, weak=True):
  144. """Disconnect receiver from sender for signal
  145. receiver -- the registered receiver to disconnect
  146. signal -- the registered signal to disconnect
  147. sender -- the registered sender to disconnect
  148. weak -- the weakref state to disconnect
  149. disconnect reverses the process of connect,
  150. the semantics for the individual elements are
  151. logically equivalent to a tuple of
  152. (receiver, signal, sender, weak) used as a key
  153. to be deleted from the internal routing tables.
  154. (The actual process is slightly more complex
  155. but the semantics are basically the same).
  156. Note:
  157. Using disconnect is not required to cleanup
  158. routing when an object is deleted, the framework
  159. will remove routes for deleted objects
  160. automatically. It's only necessary to disconnect
  161. if you want to stop routing to a live object.
  162. returns None, may raise DispatcherTypeError or
  163. DispatcherKeyError
  164. """
  165. if signal is None:
  166. raise errors.DispatcherTypeError(
  167. 'Signal cannot be None (receiver=%r sender=%r)' % (receiver,
  168. sender)
  169. )
  170. if weak: receiver = saferef.safeRef(receiver)
  171. senderkey = id(sender)
  172. try:
  173. signals = connections[senderkey]
  174. receivers = signals[signal]
  175. except KeyError:
  176. raise errors.DispatcherKeyError(
  177. """No receivers found for signal %r from sender %r""" % (
  178. signal,
  179. sender
  180. )
  181. )
  182. try:
  183. # also removes from receivers
  184. _removeOldBackRefs(senderkey, signal, receiver, receivers)
  185. except ValueError:
  186. raise errors.DispatcherKeyError(
  187. """No connection to receiver %s for signal %s from sender %s""" % (
  188. receiver,
  189. signal,
  190. sender
  191. )
  192. )
  193. _cleanupConnections(senderkey, signal)
  194. def getReceivers(sender=Any, signal=Any):
  195. """Get list of receivers from global tables
  196. This utility function allows you to retrieve the
  197. raw list of receivers from the connections table
  198. for the given sender and signal pair.
  199. Note:
  200. there is no guarantee that this is the actual list
  201. stored in the connections table, so the value
  202. should be treated as a simple iterable/truth value
  203. rather than, for instance a list to which you
  204. might append new records.
  205. Normally you would use liveReceivers(getReceivers(...))
  206. to retrieve the actual receiver objects as an iterable
  207. object.
  208. """
  209. try:
  210. return connections[id(sender)][signal]
  211. except KeyError:
  212. return []
  213. def liveReceivers(receivers):
  214. """Filter sequence of receivers to get resolved, live receivers
  215. This is a generator which will iterate over
  216. the passed sequence, checking for weak references
  217. and resolving them, then returning all live
  218. receivers.
  219. """
  220. for receiver in receivers:
  221. if isinstance(receiver, WEAKREF_TYPES):
  222. # Dereference the weak reference.
  223. receiver = receiver()
  224. if receiver is not None:
  225. yield receiver
  226. else:
  227. yield receiver
  228. def getAllReceivers(sender=Any, signal=Any):
  229. """Get list of all receivers from global tables
  230. This gets all receivers which should receive
  231. the given signal from sender, each receiver should
  232. be produced only once by the resulting generator
  233. """
  234. receivers = {}
  235. for set in (
  236. # Get receivers that receive *this* signal from *this* sender.
  237. getReceivers(sender, signal),
  238. # Add receivers that receive *any* signal from *this* sender.
  239. getReceivers(sender, Any),
  240. # Add receivers that receive *this* signal from *any* sender.
  241. getReceivers(Any, signal),
  242. # Add receivers that receive *any* signal from *any* sender.
  243. getReceivers(Any, Any),
  244. ):
  245. for receiver in set:
  246. if receiver: # filter out dead instance-method weakrefs
  247. try:
  248. if receiver not in receivers:
  249. receivers[receiver] = 1
  250. yield receiver
  251. except TypeError:
  252. # dead weakrefs raise TypeError on hash...
  253. pass
  254. def send(signal=Any, sender=Anonymous, *arguments, **named):
  255. """Send signal from sender to all connected receivers.
  256. signal -- (hashable) signal value, see connect for details
  257. sender -- the sender of the signal
  258. if Any, only receivers registered for Any will receive
  259. the message.
  260. if Anonymous, only receivers registered to receive
  261. messages from Anonymous or Any will receive the message
  262. Otherwise can be any python object (normally one
  263. registered with a connect if you actually want
  264. something to occur).
  265. arguments -- positional arguments which will be passed to
  266. *all* receivers. Note that this may raise TypeErrors
  267. if the receivers do not allow the particular arguments.
  268. Note also that arguments are applied before named
  269. arguments, so they should be used with care.
  270. named -- named arguments which will be filtered according
  271. to the parameters of the receivers to only provide those
  272. acceptable to the receiver.
  273. Return a list of tuple pairs [(receiver, response), ... ]
  274. if any receiver raises an error, the error propagates back
  275. through send, terminating the dispatch loop, so it is quite
  276. possible to not have all receivers called if a raises an
  277. error.
  278. """
  279. # Call each receiver with whatever arguments it can accept.
  280. # Return a list of tuple pairs [(receiver, response), ... ].
  281. responses = []
  282. for receiver in liveReceivers(getAllReceivers(sender, signal)):
  283. response = robustapply.robustApply(
  284. receiver,
  285. signal=signal,
  286. sender=sender,
  287. *arguments,
  288. **named
  289. )
  290. responses.append((receiver, response))
  291. return responses
  292. def sendExact(signal=Any, sender=Anonymous, *arguments, **named):
  293. """Send signal only to those receivers registered for exact message
  294. sendExact allows for avoiding Any/Anonymous registered
  295. handlers, sending only to those receivers explicitly
  296. registered for a particular signal on a particular
  297. sender.
  298. """
  299. responses = []
  300. for receiver in liveReceivers(getReceivers(sender, signal)):
  301. response = robustapply.robustApply(
  302. receiver,
  303. signal=signal,
  304. sender=sender,
  305. *arguments,
  306. **named
  307. )
  308. responses.append((receiver, response))
  309. return responses
  310. def _removeReceiver(receiver):
  311. """Remove receiver from connections."""
  312. if not sendersBack or not connections:
  313. # During module cleanup the objects will be replaced with None
  314. # The order of replacing many chnage, so both variables need
  315. # to be checked.
  316. return False
  317. backKey = id(receiver)
  318. try:
  319. backSet = sendersBack.pop(backKey)
  320. except KeyError:
  321. return False
  322. else:
  323. for senderkey in backSet:
  324. try:
  325. signals = connections[senderkey].keys()
  326. except KeyError:
  327. pass
  328. else:
  329. for signal in signals:
  330. try:
  331. receivers = connections[senderkey][signal]
  332. except KeyError:
  333. pass
  334. else:
  335. try:
  336. receivers.remove(receiver)
  337. except Exception:
  338. pass
  339. _cleanupConnections(senderkey, signal)
  340. def _cleanupConnections(senderkey, signal):
  341. """Delete any empty signals for senderkey. Delete senderkey if empty."""
  342. try:
  343. receivers = connections[senderkey][signal]
  344. except:
  345. pass
  346. else:
  347. if not receivers:
  348. # No more connected receivers. Therefore, remove the signal.
  349. try:
  350. signals = connections[senderkey]
  351. except KeyError:
  352. pass
  353. else:
  354. del signals[signal]
  355. if not signals:
  356. # No more signal connections. Therefore, remove the sender.
  357. _removeSender(senderkey)
  358. def _removeSender(senderkey):
  359. """Remove senderkey from connections."""
  360. _removeBackrefs(senderkey)
  361. try:
  362. del connections[senderkey]
  363. except KeyError:
  364. pass
  365. # Senderkey will only be in senders dictionary if sender
  366. # could be weakly referenced.
  367. try:
  368. del senders[senderkey]
  369. except:
  370. pass
  371. def _removeBackrefs(senderkey):
  372. """Remove all back-references to this senderkey"""
  373. try:
  374. signals = connections[senderkey]
  375. except KeyError:
  376. signals = None
  377. else:
  378. items = signals.items()
  379. def allReceivers():
  380. for signal, set in items:
  381. for item in set:
  382. yield item
  383. for receiver in allReceivers():
  384. _killBackref(receiver, senderkey)
  385. def _removeOldBackRefs(senderkey, signal, receiver, receivers):
  386. """Kill old sendersBack references from receiver
  387. This guards against multiple registration of the same
  388. receiver for a given signal and sender leaking memory
  389. as old back reference records build up.
  390. Also removes old receiver instance from receivers
  391. """
  392. try:
  393. index = receivers.index(receiver)
  394. # need to scan back references here and remove senderkey
  395. except ValueError:
  396. return False
  397. else:
  398. oldReceiver = receivers[index]
  399. del receivers[index]
  400. found = 0
  401. signals = connections.get(signal)
  402. if signals is not None:
  403. for sig, recs in connections.get(signal, {}).iteritems():
  404. if sig != signal:
  405. for rec in recs:
  406. if rec is oldReceiver:
  407. found = 1
  408. break
  409. if not found:
  410. _killBackref(oldReceiver, senderkey)
  411. return True
  412. return False
  413. def _killBackref(receiver, senderkey):
  414. """Do the actual removal of back reference from receiver to senderkey"""
  415. receiverkey = id(receiver)
  416. set = sendersBack.get(receiverkey, ())
  417. while senderkey in set:
  418. try:
  419. set.remove(senderkey)
  420. except:
  421. break
  422. if not set:
  423. try:
  424. del sendersBack[receiverkey]
  425. except KeyError:
  426. pass
  427. return True