Skip to content

Commit

Permalink
Stackless issue python#222: Support code for testing C-stack serial n…
Browse files Browse the repository at this point in the history
…umbers

Allow the Stackless test suite to access
- PyThreadState fields st.serial, st.serial_last_jump, st.initial_stub
- PyCStackObject attribute serial
Intentionally undocumented

(cherry picked from commit 4f7b3ec)
  • Loading branch information
Anselm Kruis committed Jul 1, 2019
1 parent 48cacfc commit bffe80b
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 7 deletions.
1 change: 1 addition & 0 deletions Stackless/core/stacklesseval.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ static PyMemberDef cstack_members[] = {
{"task", T_OBJECT, offsetof(PyCStackObject, task), READONLY},
{"startaddr", T_ADDR, offsetof(PyCStackObject, startaddr), READONLY},
{"nesting_level", T_INT, offsetof(PyCStackObject, nesting_level), READONLY},
{"serial", T_LONGLONG, offsetof(PyCStackObject, serial), READONLY},
{0}
};

Expand Down
50 changes: 43 additions & 7 deletions Stackless/module/stacklessmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -784,12 +784,15 @@ get_thread_info(PyObject *self, PyObject *args)
long id_is_valid;
/* The additional optional argument flags is currently intentionally
* undocumented. The lower order flag bits are reserved for future public
* applications. If the flag bit 31 is set, the returned tuple contains
* the watchdog list as an additional item. The Stackless test suite uses
* this feature to ensure that the list is empty at start and end of each
* test.
* applications.
* If the flag bit 30 is set, the values of serial, serial_last_jump and
* initial_stub are appended to the result. The Stackless test suite uses them.
* If the flag bit 31 is set, the watchdog list is appended to the result.
* The Stackless test suite uses this feature to ensure that the list is empty at
* start and end of each test.
*/
unsigned long flags=0;
PyObject *retval;

if (!PyArg_ParseTuple(args, "|O!k:get_thread_info", &PyLong_Type, &thread_id, &flags))
return NULL;
Expand All @@ -807,11 +810,44 @@ get_thread_info(PyObject *self, PyObject *args)
RUNTIME_ERROR("Thread id not found", NULL);
}

return Py_BuildValue((flags & (1ul<<31)) ? "(OOiO)": "(OOi)",
retval = Py_BuildValue("(OOi)",
ts->st.main ? (PyObject *) ts->st.main : Py_None,
ts->st.runcount ? (PyObject *) ts->st.current : Py_None,
ts->st.runcount,
ts->st.watchdogs ? ts->st.watchdogs : Py_None);
ts->st.runcount
);

if (retval && (flags & (1ul<<30))) {
Py_ssize_t retsize = PyTuple_GET_SIZE(retval);
/* Append the serial numbers */
PyObject *serial, *serial_last_jump;
serial = PyLong_FromLongLong(ts->st.serial);
if (serial == NULL) {
Py_DECREF(retval);
return NULL;
}
serial_last_jump = PyLong_FromLongLong(ts->st.serial_last_jump);
if (serial == NULL) {
Py_DECREF(retval);
return NULL;
}
if (!_PyTuple_Resize(&retval, retsize + 3)) {
PyTuple_SET_ITEM(retval, retsize, serial); /* steals a ref to serial */
PyTuple_SET_ITEM(retval, retsize + 1, serial_last_jump); /* steals a ref to serial_last_jump */
serial = ts->st.initial_stub ? (PyObject *) ts->st.initial_stub : Py_None;
Py_INCREF(serial);
PyTuple_SET_ITEM(retval, retsize + 2, serial); /* steals a ref to serial */
}
}
if (retval && (flags & (1ul<<31))) {
Py_ssize_t retsize = PyTuple_GET_SIZE(retval);
/* Append the watchdog list */
if (!_PyTuple_Resize(&retval, retsize + 1)) {
PyObject *o = ts->st.watchdogs ? ts->st.watchdogs : Py_None;
Py_INCREF(o);
PyTuple_SET_ITEM(retval, retsize, o); /* steals a ref to o */
}
}
return retval;
}

static PyObject *
Expand Down
11 changes: 11 additions & 0 deletions Stackless/unittests/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ def wrapper(self):
# raise RuntimeError("There is no ref leak for obj. Missing referrers %d" % (delta,))


def get_serial_last_jump(threadid=-1):
"""Get the current serial_last_jump of the given thread.
"""
# The second argument of get_thread_info() is intentionally undocumented.
# See C source.
return stackless.get_thread_info(threadid, 1 << 30)[4]


def get_watchdog_list(threadid):
"""Get the watchdog list of a thread.
Expand Down Expand Up @@ -561,6 +569,7 @@ def setUpStacklessTestCase(self):

self.__active_test_cases[id(self)] = self
self.__uncollectable_tasklets = []
self.__initial_cstack_serial = get_serial_last_jump()
self.assertListEqual([t for t in gc.garbage if isinstance(t, stackless.tasklet)], [],
"Leakage from other tests, with tasklets in gc.garbage")

Expand Down Expand Up @@ -590,6 +599,8 @@ def setUp(self):
(active_count, expected_thread_count))

def tearDown(self):
# Test that the C-stack didn't change
self.assertEqual(self.__initial_cstack_serial, get_serial_last_jump())
# Test, that stackless errorhandler is None and reset it
self.assertIsNone(stackless.set_error_handler(None))

Expand Down

0 comments on commit bffe80b

Please sign in to comment.