-
Notifications
You must be signed in to change notification settings - Fork 1
/
greenthread.py
272 lines (225 loc) · 10.7 KB
/
greenthread.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#
# Evy - a concurrent networking library for Python
#
# Unless otherwise noted, the files in Evy are under the following MIT license:
#
# Copyright (c) 2012, Alvaro Saurin
# Copyright (c) 2008-2010, Eventlet Contributors (see AUTHORS)
# Copyright (c) 2007-2010, Linden Research, Inc.
# Copyright (c) 2005-2006, Bob Ippolito
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import sys
from evy import event
from evy import hubs
from evy import timeout
from evy.hubs import timer
from evy.support import greenlets as greenlet
import warnings
__all__ = ['getcurrent', 'sleep', 'spawn', 'spawn_n', 'spawn_after', 'spawn_after_local',
'GreenThread']
getcurrent = greenlet.getcurrent
def sleep (seconds = 0):
"""
Yield control to another eligible coroutine until at least *seconds* have
elapsed.
*seconds* may be specified as an integer, or a float if fractional seconds
are desired. Calling :func:`~greenthread.sleep` with *seconds* of 0 is the
canonical way of expressing a cooperative yield. For example, if one is
looping over a large list performing an expensive calculation without
calling any socket methods, it's a good idea to call ``sleep(0)``
occasionally; otherwise nothing else will run.
"""
hub = hubs.get_hub()
current = getcurrent()
assert hub.greenlet is not current, 'do not call blocking functions from the mainloop'
timer = hub.schedule_call_global(seconds, current.switch)
try:
hub.switch()
finally:
timer.cancel()
def spawn (func, *args, **kwargs):
"""
Create a greenthread to run ``func(*args, **kwargs)``. Returns a
:class:`GreenThread` object which you can use to get the results of the
call.
Execution control returns immediately to the caller; the created greenthread is merely scheduled
to be run at the next available opportunity. Use :func:`spawn_after` to arrange for greenthreads
to be spawned after a finite delay.
"""
hub = hubs.get_hub()
g = GreenThread(hub.greenlet)
hub.schedule_call_global(0, g.switch, func, args, kwargs)
return g
def spawn_n (func, *args, **kwargs):
"""
Same as :func:`spawn`, but returns a ``greenlet`` object from which it is not possible to
retrieve either a return value or whether it raised any exceptions. This is faster than
:func:`spawn`; it is fastest if there are no keyword arguments.
If an exception is raised in the function, spawn_n prints a stack trace; the print can be
disabled by calling :func:`evy.debug.hub_exceptions` with False.
"""
return _spawn_n(0, func, args, kwargs)[1]
def spawn_after (seconds, func, *args, **kwargs):
"""
Spawns *func* after *seconds* have elapsed. It runs as scheduled even if
the current greenthread has completed.
*seconds* may be specified as an integer, or a float if fractional seconds
are desired. The *func* will be called with the given *args* and
keyword arguments *kwargs*, and will be executed within its own greenthread.
The return value of :func:`spawn_after` is a :class:`GreenThread` object,
which can be used to retrieve the results of the call.
To cancel the spawn and prevent *func* from being called,
call :meth:`GreenThread.cancel` on the return value of :func:`spawn_after`.
This will not abort the function if it's already started running, which is
generally the desired behavior. If terminating *func* regardless of whether
it's started or not is the desired behavior, call :meth:`GreenThread.kill`.
"""
hub = hubs.get_hub()
g = GreenThread(hub.greenlet)
hub.schedule_call_global(seconds, g.switch, func, args, kwargs)
return g
def spawn_after_local (seconds, func, *args, **kwargs):
"""
Spawns *func* after *seconds* have elapsed. The function will NOT be
called if the current greenthread has exited.
*seconds* may be specified as an integer, or a float if fractional seconds
are desired. The *func* will be called with the given *args* and
keyword arguments *kwargs*, and will be executed within its own greenthread.
The return value of :func:`spawn_after` is a :class:`GreenThread` object,
which can be used to retrieve the results of the call.
To cancel the spawn and prevent *func* from being called,
call :meth:`GreenThread.cancel` on the return value. This will not abort the
function if it's already started running. If terminating *func* regardless
of whether it's started or not is the desired behavior, call
:meth:`GreenThread.kill`.
"""
hub = hubs.get_hub()
g = GreenThread(hub.greenlet)
hub.schedule_call_local(seconds, g.switch, func, args, kwargs)
return g
# exc_after (seconds, *throw_args): instead of exc_after, which is deprecated, use Timeout(seconds, exception)
# deprecate, remove
TimeoutError = timeout.Timeout
with_timeout = timeout.with_timeout
def _spawn_n (seconds, func, args, kwargs):
hub = hubs.get_hub()
g = greenlet.greenlet(func, parent = hub.greenlet)
t = hub.schedule_call_global(seconds, g.switch, *args, **kwargs)
return t, g
class GreenThread(greenlet.greenlet):
"""The GreenThread class is a type of Greenlet which has the additional
property of being able to retrieve the return value of the main function.
Do not construct GreenThread objects directly; call :func:`spawn` to get one.
"""
def __init__ (self, parent):
greenlet.greenlet.__init__(self, self.main, parent)
self._exit_event = event.Event()
def wait (self):
""" Returns the result of the main function of this GreenThread. If the
result is a normal return value, :meth:`wait` returns it. If it raised
an exception, :meth:`wait` will raise the same exception (though the
stack trace will unavoidably contain some frames from within the
greenthread module)."""
return self._exit_event.wait()
def link (self, func, *curried_args, **curried_kwargs):
""" Set up a function to be called with the results of the GreenThread.
The function must have the following signature::
def func(gt, [curried args/kwargs]):
When the GreenThread finishes its run, it calls *func* with itself
and with the `curried arguments <http://en.wikipedia.org/wiki/Currying>`_ supplied at link-time. If the function wants
to retrieve the result of the GreenThread, it should call wait()
on its first argument.
Note that *func* is called within execution context of
the GreenThread, so it is possible to interfere with other linked
functions by doing things like switching explicitly to another
greenthread.
"""
self._exit_funcs = getattr(self, '_exit_funcs', [])
self._exit_funcs.append((func, curried_args, curried_kwargs))
if self._exit_event.ready():
self._resolve_links()
def main (self, function, args, kwargs):
try:
result = function(*args, **kwargs)
except:
self._exit_event.send_exception(*sys.exc_info())
self._resolve_links()
raise
else:
self._exit_event.send(result)
self._resolve_links()
def _resolve_links (self):
# ca and ckw are the curried function arguments
for f, ca, ckw in getattr(self, '_exit_funcs', []):
f(self, *ca, **ckw)
self._exit_funcs = [] # so they don't get called again
def kill (self, *throw_args):
"""Kills the greenthread using :func:`kill`. After being killed
all calls to :meth:`wait` will raise *throw_args* (which default
to :class:`greenlet.GreenletExit`)."""
return kill(self, *throw_args)
def cancel (self, *throw_args):
"""Kills the greenthread using :func:`kill`, but only if it hasn't
already started running. After being canceled,
all calls to :meth:`wait` will raise *throw_args* (which default
to :class:`greenlet.GreenletExit`)."""
return cancel(self, *throw_args)
def cancel (g, *throw_args):
"""Like :func:`kill`, but only terminates the greenthread if it hasn't
already started execution. If the grenthread has already started
execution, :func:`cancel` has no effect."""
if not g:
kill(g, *throw_args)
def kill (g, *throw_args):
"""Terminates the target greenthread by raising an exception into it.
Whatever that greenthread might be doing; be it waiting for I/O or another
primitive, it sees an exception right away.
By default, this exception is GreenletExit, but a specific exception
may be specified. *throw_args* should be the same as the arguments to
raise; either an exception instance or an exc_info tuple.
Calling :func:`kill` causes the calling greenthread to cooperatively yield.
"""
if g.dead:
return
hub = hubs.get_hub()
if not g:
# greenlet hasn't started yet and therefore throw won't work
# on its own; semantically we want it to be as though the main
# method never got called
def just_raise (*a, **kw):
if throw_args:
raise throw_args[0], throw_args[1], throw_args[2]
else:
raise greenlet.GreenletExit()
g.run = just_raise
if isinstance(g, GreenThread):
# it's a GreenThread object, so we want to call its main
# method to take advantage of the notification
try:
g.main(just_raise, (), {})
except:
pass
current = getcurrent()
if current is not hub.greenlet:
# arrange to wake the caller back up immediately
hub.ensure_greenlet()
hub.schedule_call_global(0, current.switch)
g.throw(*throw_args)