diff options
author | Jason Madden <jamadden@gmail.com> | 2020-11-19 15:51:49 -0600 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2020-11-19 15:51:49 -0600 |
commit | 79c1edd39c1554c52668ca680024d8bbed6bdc37 (patch) | |
tree | 753e1dae0f3b8fda797c2231e42fe6e3e8026dc2 | |
parent | 6986123ee9340e545bfa801b80c1c8e46fbdc7a1 (diff) | |
download | greenlet-issue215.tar.gz |
Add extra information to the repr of greenlets.issue215
Fixes #215
-rw-r--r-- | CHANGES.rst | 3 | ||||
-rw-r--r-- | src/greenlet/greenlet.c | 55 | ||||
-rw-r--r-- | src/greenlet/tests/test_greenlet.py | 91 |
3 files changed, 128 insertions, 21 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 3c80a6e..dd8fc00 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -41,6 +41,9 @@ alter that at compile time have been removed (no combination other than the defaults was ever tested). This helps define a stable ABI. +- The repr of greenlet objects now includes extra information about + its state. This is purely informative and the details are subject to + change. See `issue 215 <https://github.com/python-greenlet/greenlet/issues/215>`_. 0.4.17 (2020-09-22) diff --git a/src/greenlet/greenlet.c b/src/greenlet/greenlet.c index 11fd17a..aacfb09 100644 --- a/src/greenlet/greenlet.c +++ b/src/greenlet/greenlet.c @@ -1301,10 +1301,17 @@ green_setdict(PyGreenlet* self, PyObject* val, void* c) return 0; } +static int +_green_not_dead(PyGreenlet* self) +{ + return PyGreenlet_ACTIVE(self) || !PyGreenlet_STARTED(self); +} + + static PyObject* green_getdead(PyGreenlet* self, void* c) { - if (PyGreenlet_ACTIVE(self) || !PyGreenlet_STARTED(self)) { + if (_green_not_dead(self)) { Py_RETURN_FALSE; } else { @@ -1512,6 +1519,50 @@ green_getstate(PyGreenlet* self) return NULL; } +static PyObject* +green_repr(PyGreenlet* self) +{ + /* + Return a string like + <greenlet.greenlet at 0xdeadbeef [current][active started]|dead main> + + The handling of greenlets across threads is not super good. + We mostly use the internal definitions of these terms, but they + generally should make sense to users as well. + */ + PyObject* result; + int never_started = !PyGreenlet_STARTED(self) && !PyGreenlet_ACTIVE(self); + + if (!STATE_OK) { + return NULL; + } + + if (_green_not_dead(self)) { + result = PyUnicode_FromFormat( + "<%s object at %p (otid=%p)%s%s%s%s>", + Py_TYPE(self)->tp_name, + self, + self->run_info, + ts_current == self + ? " current" + : (PyGreenlet_STARTED(self) ? " suspended" : ""), + PyGreenlet_ACTIVE(self) ? " active" : "", + never_started ? " pending" : " started", + PyGreenlet_MAIN(self) ? " main" : "" + ); + } + else { + /* main greenlets never really appear dead. */ + result = PyUnicode_FromFormat( + "<%s object at %p (otid=%p) dead>", + Py_TYPE(self)->tp_name, + self, + self->run_info + ); + } + return result; +} + /***************************************************************************** * C interface * @@ -1662,7 +1713,7 @@ PyTypeObject PyGreenlet_Type = { 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ - 0, /* tp_repr */ + (reprfunc)green_repr, /* tp_repr */ &green_as_number, /* tp_as _number*/ 0, /* tp_as _sequence*/ 0, /* tp_as _mapping*/ diff --git a/src/greenlet/tests/test_greenlet.py b/src/greenlet/tests/test_greenlet.py index 5a3fe10..b75e423 100644 --- a/src/greenlet/tests/test_greenlet.py +++ b/src/greenlet/tests/test_greenlet.py @@ -3,15 +3,10 @@ import sys import time import threading import unittest +from abc import ABCMeta, abstractmethod from greenlet import greenlet -try: - from abc import ABCMeta, abstractmethod -except ImportError: - ABCMeta = None - abstractmethod = None - class SomeError(Exception): pass @@ -469,22 +464,22 @@ class GreenletTests(unittest.TestCase): g = greenlet(switchapply) self.assertEqual(g.switch(), kwargs) - if ABCMeta is not None and abstractmethod is not None: - def test_abstract_subclasses(self): - AbstractSubclass = ABCMeta( - 'AbstractSubclass', - (greenlet,), - {'run': abstractmethod(lambda self: None)}) - class BadSubclass(AbstractSubclass): - pass + def test_abstract_subclasses(self): + AbstractSubclass = ABCMeta( + 'AbstractSubclass', + (greenlet,), + {'run': abstractmethod(lambda self: None)}) - class GoodSubclass(AbstractSubclass): - def run(self): - pass + class BadSubclass(AbstractSubclass): + pass - GoodSubclass() # should not raise - self.assertRaises(TypeError, BadSubclass) + class GoodSubclass(AbstractSubclass): + def run(self): + pass + + GoodSubclass() # should not raise + self.assertRaises(TypeError, BadSubclass) def test_implicit_parent_with_threads(self): if not gc.isenabled(): @@ -540,3 +535,61 @@ class GreenletTests(unittest.TestCase): for i in range(5): if attempt(): break + +class TestRepr(unittest.TestCase): + + def assertEndsWith(self, got, suffix): + self.assertTrue(got.endswith(suffix), (got, suffix)) + + def test_main_while_running(self): + r = repr(greenlet.getcurrent()) + self.assertEndsWith(r, " current active started main>") + + def test_main_in_background(self): + main = greenlet.getcurrent() + def run(): + return repr(main) + + g = greenlet(run) + r = g.switch() + self.assertEndsWith(r, ' suspended active started main>') + + def test_initial(self): + r = repr(greenlet()) + self.assertEndsWith(r, ' pending>') + + def test_main_from_other_thread(self): + main = greenlet.getcurrent() + + class T(threading.Thread): + original_main = thread_main = None + main_glet = None + def run(self): + self.original_main = repr(main) + self.main_glet = greenlet.getcurrent() + self.thread_main = repr(self.main_glet) + + t = T() + t.start() + t.join(10) + + self.assertEndsWith(t.original_main, ' suspended active started main>') + self.assertEndsWith(t.thread_main, ' current active started main>') + + r = repr(t.main_glet) + # main greenlets, even from dead threads, never really appear dead + # TODO: Can we find a better way to differentiate that? + assert not t.main_glet.dead + self.assertEndsWith(r, ' suspended active started main>') + + + def test_dead(self): + g = greenlet(lambda: None) + g.switch() + self.assertEndsWith(repr(g), ' dead>') + self.assertNotIn('suspended', repr(g)) + self.assertNotIn('started', repr(g)) + self.assertNotIn('active', repr(g)) + +if __name__ == '__main__': + unittest.main() |