Package tangled :: Module core
[hide private]
[frames] | no frames]

Source Code for Module tangled.core

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (c) 2001-2010 Pär Bohrarper. 
  4  # See LICENSE for details. 
  5   
  6  import asynchat 
  7  import asyncore 
  8  import socket 
  9  import threading 
 10  import functools 
 11  import Queue 
 12  import sys 
 13  import time 
 14  import heapq 
 15  import select 
 16  import traceback 
 17   
 18  import logging 
 19  log = logging.getLogger("tangled.core") 
 20   
 21  __version__ = "0.1.1.1" 
 22   
23 -def succeed(r):
24 """Syntactic sugar for making a synchronous call look asynchronous""" 25 d = Deferred() 26 d.callback(r) 27 return d
28
29 -def fail(r):
30 """Syntactic sugar for making a synchronous call look asynchronous, failure version""" 31 d = Deferred() 32 d.callback(r) 33 return d
34
35 -def passthru(r):
36 """A callback/errback that doesn't do anything""" 37 return r
38
39 -class Worker(threading.Thread):
40 """ 41 This is a worker thread which executes a function and calls a callback on completion 42 43 @param reactor: The reactor this is a worker for. 44 @type reactor: L{Reactor} 45 @param autostart: If true, the worker thread starts immediately. Otherwise start() has to be called. 46 """
47 - def __init__(self, reactor, autostart=False):
48 threading.Thread.__init__(self, target=self._runner) 49 self._queue = Queue.Queue() 50 self.reactor = reactor 51 self._running = True 52 self.daemon = True 53 if autostart: 54 self.start()
55
56 - def stop(self):
57 """Stops the worker""" 58 self._running = False
59
60 - def _runner(self):
61 """The message pump of the worker""" 62 while self._running: 63 try: 64 func, oncomplete = self._queue.get(block=True, timeout=1) 65 except Queue.Empty: 66 pass 67 else: 68 res = None 69 try: 70 res = func() 71 except: 72 res = Failure() 73 oncomplete(res)
74
75 - def execute(self, func, oncomplete):
76 """ 77 Executes func in the worker thread, which then calls oncomplete 78 79 @type func: callable 80 @type oncomplete: callable 81 """ 82 self._queue.put((func, oncomplete))
83
84 - def defer(self, func):
85 """ 86 Defers the call to func to this worker 87 88 @param func: The function you want the worker to call 89 @type func: callable 90 @return: A L{Deferred} object that will eventually get the result of func 91 @rtype: L{Deferred} 92 """ 93 return self.reactor.defer_to_worker(func, self)
94
95 -class Failure(object):
96 """Like Twisted's Failure object, but with no features"""
97 - def __init__(self, type_=None):
98 if type_ is None: 99 self.type, self.value, self.tb = sys.exc_info() 100 else: 101 self.type = type_ 102 self.value = None 103 self.tb = None
104
105 - def __str__(self):
106 return repr(traceback.format_exception(self.type, self.value, self.tb))
107
108 - def raise_exception(self):
109 """Re-raises the exception""" 110 raise self.type, self.value, self.tb
111
112 - def check(self, *exceptions):
113 """ 114 This can be used to have try/except like blocks in your errback 115 """ 116 for e in exceptions: 117 if isinstance(self.type, e): 118 return True 119 return False
120 121
122 -class Deferred(object):
123 """Very similar to Twisted's Deferred object, but with less features"""
124 - def __init__(self):
125 self.callbacks = [] 126 self.called = False 127 self.paused = 0
128
129 - def _start_callbacks(self, result):
130 if not self.called: 131 self.called = True 132 self.result = result 133 self._run_callbacks()
134
135 - def _run_callbacks(self):
136 if not self.paused: 137 while self.callbacks: 138 try: 139 cb, eb = self.callbacks.pop(0) 140 if isinstance(self.result, Failure): 141 cb = eb 142 self.result = cb(self.result) 143 if isinstance(self.result, Deferred): 144 self.pause() 145 # This will cause the callback chain to resume later, 146 # or immediately (recursively) if result is already 147 # available 148 self.add_both(self._continue) 149 except: 150 self.result = Failure() 151 if isinstance(self.result, Failure): 152 log.error("Unhandled Failure: %s", self.result)
153
154 - def add_callback(self, cb):
155 """See L{add_callbacks}""" 156 self.add_callbacks(cb)
157
158 - def add_errback(self, eb):
159 """ 160 Adds errback only (a passthru will be used for the callback, so the result is not lost) 161 162 @param eb: errback 163 """ 164 self.add_callbacks(passthru, eb)
165
166 - def add_both(self, cb):
167 """ 168 Adds one function as both callback and errback 169 170 @param cb: callback and errback 171 """ 172 self.add_callbacks(cb, cb)
173
174 - def add_callbacks(self, cb, eb=None):
175 """ 176 Adds callback and errback 177 178 @param cb: callback 179 @param eb: errback 180 """ 181 self.callbacks.append((cb, eb or passthru)) 182 if self.called: 183 self._run_callbacks()
184
185 - def pause(self):
186 self.paused = self.paused + 1
187
188 - def unpause(self):
189 self.paused = self.paused - 1 190 if self.paused > 0: 191 return 192 if self.called: 193 self._run_callbacks()
194
195 - def _continue(self, result):
196 self.result = result 197 self.unpause()
198
199 - def callback(self, result):
200 self._start_callbacks(result)
201
202 - def errback(self, fail=None):
203 if not isinstance(fail, Failure): 204 fail = Failure(fail) 205 self._start_callbacks(fail)
206 207
208 -def set_reuse_addr(s):
209 """Try to re-use a server port if possible. Useful in development.""" 210 try: 211 s.setsockopt( 212 socket.SOL_SOCKET, socket.SO_REUSEADDR, 213 s.getsockopt(socket.SOL_SOCKET, 214 socket.SO_REUSEADDR) | 1 215 ) 216 except socket.error: 217 pass
218
219 -class BindError(Exception):
220 pass
221
222 -class Trigger(asyncore.dispatcher):
223 """Used to trigger the event loop with external stuff, 224 also from Medusa""" 225
226 - def __init__(self):
227 a = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 228 w = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 229 set_reuse_addr(a) 230 set_reuse_addr(w) 231 232 # set TCP_NODELAY to true to avoid buffering 233 w.setsockopt(socket.IPPROTO_TCP, 1, 1) 234 235 # tricky: get a pair of connected sockets 236 host='127.0.0.1' 237 port=19999 238 while 1: 239 try: 240 self.address = (host, port) 241 a.bind(self.address) 242 break 243 except: 244 if port <= 19950: 245 raise BindError 246 port = port - 1 247 248 a.listen(1) 249 w.setblocking(0) 250 try: 251 w.connect(self.address) 252 except: 253 pass 254 r, addr = a.accept() 255 a.close() 256 w.setblocking(1) 257 self.trigger = w 258 259 self.lock = threading.Lock() 260 self.funcs = [] 261 262 asyncore.dispatcher.__init__(self, r)
263
264 - def readable(self):
265 return 1
266
267 - def writable(self):
268 return 0
269
270 - def handle_connect(self):
271 pass
272
273 - def pull_trigger(self, func=None):
274 if func: 275 try: 276 self.lock.acquire() 277 self.funcs.append(func) 278 finally: 279 self.lock.release() 280 self.trigger.send('x')
281
282 - def handle_read(self):
283 self.recv(8192) 284 try: 285 self.lock.acquire() 286 for func in self.funcs: 287 func() 288 self.funcs = [] 289 finally: 290 self.lock.release()
291
292 -class Reactor(object):
293 """The reactor is the engine of your asynchronous application.""" 294 # trigger object to wake the loop 295 _trigger = Trigger() 296 use_poll = False 297
298 - def __init__(self):
299 self._pending_calls = []
300
301 - def wake(self):
302 """Uses the trigger to wake the async loop""" 303 self._trigger.pull_trigger()
304
305 - def run_in_main(self, func):
306 """ 307 Wakes the async loop, and calls func in it 308 309 @param func: The function to call from the main loop 310 """ 311 self._trigger.pull_trigger(func)
312
313 - def defer_to_worker(self, func, worker):
314 """ 315 Calls a function in a worker, and return the result as a L{Deferred} 316 317 @param func: The function to call in the worker 318 @param worker: The worker that should handle the call 319 @type worker: L{Worker} 320 @return: A L{Deferred} objeft that will eventually contain the result 321 @rtype: L{Deferred} 322 """ 323 d = Deferred() 324 def callback(result): 325 if isinstance(result, Failure): 326 self.run_in_main(functools.partial(d.errback, result)) 327 else: 328 self.run_in_main(functools.partial(d.callback, result))
329 worker.execute(func, callback) 330 return d
331
332 - def call_later(self, func, timeout):
333 """ 334 Call a function at a later time in the main loop 335 336 @param func: The function to call 337 @param timeout: How long (in seconds) to the call shall be made 338 """ 339 heapq.heappush(self._pending_calls, (time.time() + timeout, func)) 340 self.wake()
341
342 - def _timeout(self):
343 if not self._pending_calls: 344 return None 345 return max(0, self._pending_calls[0][0] - time.time())
346
347 - def loop(self):
348 if self.use_poll and hasattr(select, 'poll'): 349 poll_fun = asyncore.poll2 350 else: 351 poll_fun = asyncore.poll 352 353 while asyncore.socket_map: 354 timeout = self._timeout() 355 poll_fun(timeout, asyncore.socket_map) 356 # check expired timeouts 357 t = time.time() 358 while self._pending_calls: 359 timeout, func = self._pending_calls[0] 360 if timeout < t: 361 func() 362 heapq.heappop(self._pending_calls) 363 else: 364 # No timeout 365 break
366