Commit d22eddc1 authored by Denis Bilenko's avatar Denis Bilenko

Greenlet: pass args and kwargs to __init__() instead of start()

- start event is stored as an attribute; it is cancelled by throw()
- spawn_later returns Greenlet instace, not tuple (greenlet, event)
- _run / args / kwargs attributes are removed when Greenlet finishes execution
parent 707dcb03
...@@ -137,18 +137,25 @@ class FailureGreenletLink(GreenletLink): ...@@ -137,18 +137,25 @@ class FailureGreenletLink(GreenletLink):
class Greenlet(greenlet): class Greenlet(greenlet):
# QQQ rename to Microthread
"""A greenlet subclass that adds a few features. """A greenlet subclass that adds a few features.
""" """
def __init__(self, run=None): args = ()
if run is not None: kwargs = {}
self._run = run # subclasses should override _run() (not run())
def __init__(self, run=None, *args, **kwargs):
greenlet.__init__(self, parent=get_hub()) greenlet.__init__(self, parent=get_hub())
if run is not None:
self._run = run
if args:
self.args = args
if kwargs:
self.kwargs = kwargs
self._links = set() self._links = set()
self.value = None self.value = None
self._exception = _NONE self._exception = _NONE
self._notifier = None self._notifier = None
self._start_event = None
def ready(self): def ready(self):
return self.dead or self._exception is not _NONE return self.dead or self._exception is not _NONE
...@@ -158,17 +165,29 @@ class Greenlet(greenlet): ...@@ -158,17 +165,29 @@ class Greenlet(greenlet):
def __repr__(self): def __repr__(self):
classname = self.__class__.__name__ classname = self.__class__.__name__
try:
funcname = getfuncname(self.__dict__['_run'])
except Exception:
funcname = None
result = '<%s at %s' % (classname, hex(id(self))) result = '<%s at %s' % (classname, hex(id(self)))
if funcname is not None: formatted = self._formatted_info
result += ': %s' % funcname if formatted is not None:
result += ': ' + formatted
return result + '>' return result + '>'
@property
def _formatted_info(self):
try:
result = getfuncname(self.__dict__['_run'])
except Exception:
pass
else:
args = []
if self.args:
args = [repr(x) for x in self.args]
if self.kwargs:
args.extend(['%s=%r' % x for x in self.kwargs.items()])
if args:
result += '(' + ', '.join(args) + ')'
self.__dict__['_formatted_func'] = result
return result
@property @property
def exception(self): def exception(self):
"""If greenlet has failed, 'exception' property holds the exception instance.""" """If greenlet has failed, 'exception' property holds the exception instance."""
...@@ -176,11 +195,14 @@ class Greenlet(greenlet): ...@@ -176,11 +195,14 @@ class Greenlet(greenlet):
return self._exception return self._exception
def throw(self, *args): def throw(self, *args):
if self._start_event is not None:
self._start_event.cancel()
self._start_event = None
if not self.dead: if not self.dead:
if self: if self:
return greenlet.throw(self, *args) return greenlet.throw(self, *args)
else: else:
# special case for when greenlet is not yet started # special case for when greenlet is not yet started, because _report_error is not executed
if len(args)==1: if len(args)==1:
self._exception = args[0] self._exception = args[0]
elif not args: elif not args:
...@@ -193,35 +215,27 @@ class Greenlet(greenlet): ...@@ -193,35 +215,27 @@ class Greenlet(greenlet):
if self._links and self._notifier is None: if self._links and self._notifier is None:
self._notifier = core.active_event(self._notify_links) self._notifier = core.active_event(self._notify_links)
def start(self, *args): def start(self):
"""Must be called _exactly_ once for a greenlet to become active""" """Must be called _exactly_ once for a greenlet to become active"""
return core.active_event(self.switch, *args) assert self._start_event is None, 'Greenlet already started'
self._start_event = core.active_event(self.switch)
def start_later(self, seconds, *args): def start_later(self, seconds):
"""Must be called _exactly_ once for a greenlet to become active""" """Must be called _exactly_ once for a greenlet to become active"""
return core.timer(seconds, self.switch, *args) assert self._start_event is None, 'Greenlet already started'
self._start_event = core.timer(seconds, self.switch)
@classmethod @classmethod
def spawn(cls, function=None, *args, **kwargs): def spawn(cls, *args, **kwargs):
if kwargs: g = cls(*args, **kwargs)
g = cls(_switch_helper) g.start()
g.start(function, args, kwargs) return g
return g
else:
g = cls(function)
g.start(*args)
return g
@classmethod @classmethod
def spawn_later(cls, seconds, function=None, *args, **kwargs): def spawn_later(cls, seconds, *args, **kwargs):
if kwargs: g = cls(*args, **kwargs)
g = cls(_switch_helper) g.start_later(seconds)
timer = g.start_later(seconds, function, args, kwargs) return g
return g, timer
else:
g = cls(function)
timer = g.start_later(seconds, *args)
return g, timer
@classmethod @classmethod
def spawn_link(cls, function, *args, **kwargs): def spawn_link(cls, function, *args, **kwargs):
...@@ -293,40 +307,44 @@ class Greenlet(greenlet): ...@@ -293,40 +307,44 @@ class Greenlet(greenlet):
self.unlink(switch) self.unlink(switch)
raise raise
def _report_result(self, result, args): def _report_result(self, result):
self._exception = None self._exception = None
self.value = result self.value = result
if self._links and self._notifier is None: if self._links and self._notifier is None:
self._notifier = core.active_event(self._notify_links) self._notifier = core.active_event(self._notify_links)
def _report_error(self, exc_info, args): def _report_error(self, exc_info):
try: try:
traceback.print_exception(*exc_info) if exc_info[0] is not None:
info = str(self) traceback.print_exception(*exc_info)
finally: finally:
self._exception = exc_info[1] self._exception = exc_info[1]
if self._links and self._notifier is None: if self._links and self._notifier is None:
self._notifier = core.active_event(self._notify_links) self._notifier = core.active_event(self._notify_links)
info = str(self) + ' failed with '
# put the printed traceback in context
if args:
info += ' (' + ', '.join(repr(x) for x in args) + ')'
info += ' failed with '
try: try:
info += self._exception.__class__.__name__ info += self._exception.__class__.__name__
except: except Exception:
info += str(self._exception) or repr(self._exception) info += str(self._exception) or repr(self._exception)
sys.stderr.write(info + '\n\n') sys.stderr.write(info + '\n\n')
def run(self, *args): def run(self):
try: try:
result = self._run(*args) self._start_event = None
except GreenletExit, ex: try:
result = ex result = self._run(*self.args, **self.kwargs)
except: except GreenletExit, ex:
self._report_error(sys.exc_info(), args) result = ex
return except:
self._report_result(result, args) self._report_error(sys.exc_info())
return
self._report_result(result)
finally:
self.__dict__.pop('_run', None)
self.__dict__.pop('args', None)
self.__dict__.pop('kwargs', None)
def rawlink(self, callback): def rawlink(self, callback):
if not callable(callback): if not callable(callback):
...@@ -396,11 +414,6 @@ def _kill(greenlet, exception, waiter): ...@@ -396,11 +414,6 @@ def _kill(greenlet, exception, waiter):
waiter.switch() waiter.switch()
def _switch_helper(function, args, kwargs):
# work around the fact that greenlet.switch does not support keyword args
return function(*args, **kwargs)
def joinall(greenlets, timeout=None, raise_error=False): def joinall(greenlets, timeout=None, raise_error=False):
from gevent.queue import Queue from gevent.queue import Queue
queue = Queue() queue = Queue()
......
from collections import deque from collections import deque
from gevent.hub import GreenletExit, getcurrent from gevent.hub import GreenletExit, getcurrent
from gevent.greenlet import spawn, joinall, Greenlet, _switch_helper from gevent.greenlet import spawn, joinall, Greenlet
from gevent.timeout import Timeout from gevent.timeout import Timeout
...@@ -177,31 +177,27 @@ class Pool(GreenletSet): ...@@ -177,31 +177,27 @@ class Pool(GreenletSet):
return 1 return 1
return max(0, self.size - len(self) - len(self.waiting)) return max(0, self.size - len(self) - len(self.waiting))
def start(self, g, *args): def start(self, g):
if self.size is not None and len(self) >= self.size: if self.size is not None and len(self) >= self.size:
self.waiting.append((g, args)) self.waiting.append(g)
else: else:
g.start(*args) g.start()
self.add(g) self.add(g)
def spawn(self, function, *args, **kwargs): def spawn(self, function, *args, **kwargs):
if kwargs: g = Greenlet(function, *args, **kwargs)
g = Greenlet(_switch_helper) self.start(g)
args = (function, args, kwargs)
else:
g = Greenlet(function)
self.start(g, *args)
return g return g
def discard(self, p): def discard(self, p):
GreenletSet.discard(self, p) GreenletSet.discard(self, p)
while self.waiting and len(self) < self.size: while self.waiting and len(self) < self.size:
g, args = self.waiting.popleft() g = self.waiting.popleft()
g.start(*args) g.start()
self.add(g) self.add(g)
def kill(self, exception=GreenletExit, block=False, timeout=None): def kill(self, exception=GreenletExit, block=False, timeout=None):
for g, args in self.waiting: for g in self.waiting:
g.kill(exception) g.kill(exception)
self.waiting.clear() self.waiting.clear()
return GreenletSet.kill(self, exception=exception, block=block, timeout=timeout) return GreenletSet.kill(self, exception=exception, block=block, timeout=timeout)
......
...@@ -53,14 +53,14 @@ class Test(greentest.TestCase): ...@@ -53,14 +53,14 @@ class Test(greentest.TestCase):
def test_killing_not_yet_started(self): def test_killing_not_yet_started(self):
def func(): def func():
pass pass
g, timer = gevent.spawn_later(2000, func) g = gevent.spawn_later(2000, func)
try: try:
assert not g.dead, g assert not g.dead, g
g.kill(block=True) g.kill(block=True)
assert g.dead, g assert g.dead, g
self.assertRaises(Exception, g.get) self.assertRaises(Exception, g.get)
finally: finally:
timer.cancel() g.kill(block=True)
def test_sleep_invalid_switch(self): def test_sleep_invalid_switch(self):
p = gevent.spawn(util.wrap_errors(AssertionError, gevent.sleep), 2) p = gevent.spawn(util.wrap_errors(AssertionError, gevent.sleep), 2)
......
...@@ -48,7 +48,7 @@ class TestEvent(greentest.TestCase): ...@@ -48,7 +48,7 @@ class TestEvent(greentest.TestCase):
event1 = coros.event() event1 = coros.event()
event2 = coros.event() event2 = coros.event()
g, timer = gevent.spawn_later(DELAY/2.0, event1.send, 'hello event1') g = gevent.spawn_later(DELAY/2.0, event1.send, 'hello event1')
t = gevent.Timeout(0, ValueError('interrupted')) t = gevent.Timeout(0, ValueError('interrupted'))
try: try:
try: try:
...@@ -60,7 +60,6 @@ class TestEvent(greentest.TestCase): ...@@ -60,7 +60,6 @@ class TestEvent(greentest.TestCase):
finally: finally:
t.cancel() t.cancel()
g.kill(block=True) g.kill(block=True)
timer.cancel()
if __name__=='__main__': if __name__=='__main__':
......
...@@ -59,12 +59,12 @@ class TestLink(greentest.TestCase): ...@@ -59,12 +59,12 @@ class TestLink(greentest.TestCase):
def test_link_to_inactive_greenlet(self): def test_link_to_inactive_greenlet(self):
p = gevent.spawn(lambda : 100) p = gevent.spawn(lambda : 100)
receiver, timer = gevent.spawn_later(10000, sleep, 1) receiver = gevent.spawn_later(10000, sleep, 1)
try: try:
p.link(receiver) p.link(receiver)
self.assertRaises(greenlet.LinkedCompleted, receiver.get) self.assertRaises(greenlet.LinkedCompleted, receiver.get)
finally: finally:
timer.cancel() receiver.kill(block=True)
def test_link_to_event(self): def test_link_to_event(self):
p = gevent.spawn(lambda : 100) p = gevent.spawn(lambda : 100)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment