|
1 | 1 | from __future__ import absolute_import
|
| 2 | + |
2 | 3 | import unittest
|
| 4 | +import threading |
| 5 | +import stackless |
3 | 6 | from stackless import test_cframe_nr, test_outside
|
4 | 7 | from stackless import tasklet, channel, run
|
5 | 8 | from _teststackless import test_cframe, test_cstate
|
6 | 9 |
|
7 | 10 | from support import test_main # @UnusedImport
|
8 |
| -from support import StacklessTestCase |
| 11 | +from support import StacklessTestCase, withThreads, get_serial_last_jump |
| 12 | + |
| 13 | + |
| 14 | +def current_initial_stub(threadid=-1): |
| 15 | + """Get the initial stub of the given thread. |
| 16 | + """ |
| 17 | + # The second argument of get_thread_info() is intentionally undocumented. |
| 18 | + # See C source. |
| 19 | + return stackless.get_thread_info(threadid, 1 << 30)[5] |
| 20 | + |
| 21 | + |
| 22 | +MAIN_INITIAL_STUB = current_initial_stub() |
9 | 23 |
|
10 | 24 |
|
11 | 25 | class TestOutside(StacklessTestCase):
|
@@ -70,6 +84,77 @@ def test_outside5(self):
|
70 | 84 | tasklet(test_cframe_nr)(100)
|
71 | 85 | test_outside()
|
72 | 86 |
|
| 87 | + @unittest.skipUnless(withThreads, "requires thread support") |
| 88 | + def test_main_exit_with_serial_mismatch(self): |
| 89 | + # Test the exit of a main-tasklet with a current C-stack serial number |
| 90 | + # unequal to the serial number of the initial stub. |
| 91 | + # The purpose of this test is to exercise a special code path in |
| 92 | + # slp_tasklet_end(PyObject *retval), scheduling.c:1444 |
| 93 | + |
| 94 | + # Note: this test only works in thread for two reasons: |
| 95 | + # - The main tasklet terminates |
| 96 | + # - The sequence of operations used to change the serial of the |
| 97 | + # main tasklet depends on the fact, that the thread state is |
| 98 | + # not the initial thread state of the interpreter. |
| 99 | + |
| 100 | + # sanity checks |
| 101 | + self.assertEqual(get_serial_last_jump(), MAIN_INITIAL_STUB.serial) |
| 102 | + self.result = None |
| 103 | + |
| 104 | + def run(): |
| 105 | + try: |
| 106 | + # Test preconditions: current is main at level 0 |
| 107 | + if stackless.enable_softswitch(None): |
| 108 | + self.assertEqual(stackless.current.nesting_level, 0) |
| 109 | + self.assertIs(stackless.current, stackless.main) |
| 110 | + thread_initial_stub = current_initial_stub() |
| 111 | + # sanity check |
| 112 | + self.assertEqual(get_serial_last_jump(), thread_initial_stub.serial) |
| 113 | + |
| 114 | + # a tasklet with a different C-stack |
| 115 | + # test_cstate forces a hard switch from schedule_remove |
| 116 | + t = tasklet(test_cstate)(stackless.schedule_remove) |
| 117 | + t_cstate = t.cstate |
| 118 | + |
| 119 | + # now t has a cstate, that belongs to thread_initial_stub |
| 120 | + # check it |
| 121 | + self.assertIsNot(t_cstate, thread_initial_stub) |
| 122 | + self.assertEqual(t_cstate.serial, thread_initial_stub.serial) |
| 123 | + |
| 124 | + # Run t from a new entry point. |
| 125 | + # Hard switch to t, |
| 126 | + # run t and |
| 127 | + # hard switch back to the main tasklet |
| 128 | + test_outside() # run scheduled tasklets |
| 129 | + self.assertEqual(t.nesting_level, 1) # It was a hard switch back to main |
| 130 | + |
| 131 | + # t has now it's own stack created from a different entry point |
| 132 | + self.assertIsNot(t.cstate, t_cstate) |
| 133 | + self.assertNotEqual(t.cstate.serial, thread_initial_stub.serial) |
| 134 | + t_serial = t.cstate.serial |
| 135 | + |
| 136 | + # soft-to-hard switch to t, finish t and soft-switch back |
| 137 | + t.run() |
| 138 | + |
| 139 | + self.assertEqual(t.nesting_level, 0) # Soft switching was possible |
| 140 | + if stackless.enable_softswitch(None): |
| 141 | + # Final test: the current serial has changed. |
| 142 | + self.assertNotEqual(get_serial_last_jump(), thread_initial_stub.serial) |
| 143 | + self.assertEqual(get_serial_last_jump(), t_serial) |
| 144 | + else: |
| 145 | + self.assertEqual(get_serial_last_jump(), thread_initial_stub.serial) |
| 146 | + except Exception as e: |
| 147 | + self.result = e |
| 148 | + else: |
| 149 | + self.result = False |
| 150 | + |
| 151 | + thread = threading.Thread(target=run, name=str(self.id())) |
| 152 | + thread.start() |
| 153 | + thread.join() |
| 154 | + if self.result: |
| 155 | + raise self.result |
| 156 | + self.assertIs(self.result, False) |
| 157 | + |
73 | 158 |
|
74 | 159 | class TestCframe(StacklessTestCase):
|
75 | 160 | n = 100
|
|
0 commit comments