1
2
3
4
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
24 """Syntactic sugar for making a synchronous call look asynchronous"""
25 d = Deferred()
26 d.callback(r)
27 return d
28
30 """Syntactic sugar for making a synchronous call look asynchronous, failure version"""
31 d = Deferred()
32 d.callback(r)
33 return d
34
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
57 """Stops the worker"""
58 self._running = False
59
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
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
96 """Like Twisted's Failure object, but with no features"""
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
106 return repr(traceback.format_exception(self.type, self.value, self.tb))
107
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
123 """Very similar to Twisted's Deferred object, but with less features"""
125 self.callbacks = []
126 self.called = False
127 self.paused = 0
128
130 if not self.called:
131 self.called = True
132 self.result = result
133 self._run_callbacks()
134
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
146
147
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
157
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
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
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
186 self.paused = self.paused + 1
187
189 self.paused = self.paused - 1
190 if self.paused > 0:
191 return
192 if self.called:
193 self._run_callbacks()
194
196 self.result = result
197 self.unpause()
198
201
206
207
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
221
222 -class Trigger(asyncore.dispatcher):
223 """Used to trigger the event loop with external stuff,
224 also from Medusa"""
225
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
233 w.setsockopt(socket.IPPROTO_TCP, 1, 1)
234
235
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
266
269
272
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
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
293 """The reactor is the engine of your asynchronous application."""
294
295 _trigger = Trigger()
296 use_poll = False
297
299 self._pending_calls = []
300
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
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
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
343 if not self._pending_calls:
344 return None
345 return max(0, self._pending_calls[0][0] - time.time())
346
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
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
365 break
366