summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-11-19 15:51:49 -0600
committerJason Madden <jamadden@gmail.com>2020-11-19 15:51:49 -0600
commit79c1edd39c1554c52668ca680024d8bbed6bdc37 (patch)
tree753e1dae0f3b8fda797c2231e42fe6e3e8026dc2
parent6986123ee9340e545bfa801b80c1c8e46fbdc7a1 (diff)
downloadgreenlet-issue215.tar.gz
Add extra information to the repr of greenlets.issue215
Fixes #215
-rw-r--r--CHANGES.rst3
-rw-r--r--src/greenlet/greenlet.c55
-rw-r--r--src/greenlet/tests/test_greenlet.py91
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()