Skip to content

Commit deaf98c

Browse files
author
Anselm Kruis
committed
Stackless issue python#85: Failed assertion caused by PyStackless_RunWatchdogEx
Fix issue python#85: don't schedule already scheduled tasklets. Ensure that the internal watchdog list is empty before and after all test cases of class TestNewWatchdog. Fix test case TestNewWatchdog.test_watchdog_priority_{hard|soft}. The timeout value was much to short to build up a list of watchdogs. The new test cases TestNewWatchdog.test_schedule_deeper_{hard|soft} triggers the assertion failure reliably. They are skipped for now. https://bitbucket.org/stackless-dev/stackless/issues/85 (grafted from 2235702eb90cb0709dd40544b3952a956f2032a9 and 3fb55ce5b2d4)
1 parent 04a33e3 commit deaf98c

File tree

3 files changed

+97
-14
lines changed

3 files changed

+97
-14
lines changed

Stackless/changelog.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Stackless 3.X.X?
1010

1111
*Release date: 20XX-XX-XX*
1212

13+
- https://bitbucket.org/stackless-dev/stackless/issue/85
14+
Fix a rare problem in the watchdog logic.
15+
1316
- https://bitbucket.org/stackless-dev/stackless/issue/80
1417
Fix an assertion failure during shutdown. The assertion was added by
1518
the first attempt to fix issue #60. Thanks to Masamitsu Murase

Stackless/module/stacklessmodule.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -542,13 +542,17 @@ PyStackless_RunWatchdogEx(long timeout, int flags)
542542
*/
543543
for(;;) {
544544
PyTaskletObject *popped = pop_watchdog(ts);
545-
if (popped != old_current)
546-
/* popped a deeper tasklet. */
545+
if (!PyTasklet_Scheduled(popped))
546+
/* popped a deeper tasklet, that hasn't been scheduled meanwhile */
547547
slp_current_insert(popped); /* steals reference */
548548
else {
549-
Py_DECREF(popped); /* we are already in st->ts.current */
550-
break;
549+
/* Usually only the current tasklet.
550+
* But also a deeper tasklet, that got scheduled (e.g. by taskler.insert())
551+
*/
552+
Py_DECREF(popped);
551553
}
554+
if (popped == old_current)
555+
break;
552556
}
553557
if (interrupt) {
554558
ts->st.interrupt = old_interrupt;

Stackless/unittests/test_watchdog.py

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,40 @@ def is_soft():
1515
return softswitch
1616

1717

18+
def get_current_watchdog_list():
19+
import gc
20+
result = []
21+
22+
def _tasklet(tlet):
23+
if tlet is not None:
24+
assert tlet.paused
25+
gc.collect()
26+
referrers = gc.get_referrers(tlet)
27+
for obj in referrers:
28+
if isinstance(obj, list):
29+
result.append(obj)
30+
assert len(result) == 1, "list references %d" % len(l)
31+
else:
32+
stackless.tasklet(_tasklet)(stackless.current)
33+
stackless.run()
34+
scheduled = []
35+
t = stackless.current.next
36+
while t not in (None, stackless.current):
37+
scheduled.append(t)
38+
t = t.next
39+
for t in scheduled:
40+
t.remove()
41+
stackless.tasklet(_tasklet)(None)
42+
try:
43+
stackless.run()
44+
finally:
45+
for t in scheduled:
46+
t.insert()
47+
if scheduled:
48+
assert stackless.current.next == scheduled[0]
49+
return result[0]
50+
51+
1852
class SimpleScheduler(object):
1953
""" Not really scheduler as such but used here to implement
2054
autoscheduling hack and store a schedule count. """
@@ -394,6 +428,16 @@ def setUp(self):
394428
super(TestNewWatchdog, self).setUp()
395429
self.done = 0
396430
self.worker = stackless.tasklet(self.worker_func)()
431+
self.watchdog_list = get_current_watchdog_list()
432+
self.assertListEqual(self.watchdog_list, [None], "Watchdog list is not empty before test: %r" % (self.watchdog_list,))
433+
434+
def tearDown(self):
435+
try:
436+
self.assertListEqual(self.watchdog_list, [None], "Watchdog list is not empty after test: %r" % (self.watchdog_list,))
437+
except AssertionError:
438+
self.watchdog_list[0] = None
439+
self.watchdog_list[1:] = []
440+
super(TestNewWatchdog, self).tearDown()
397441

398442
def test_run_from_worker(self):
399443
"""Test that run() works from a different tasklet"""
@@ -538,7 +582,7 @@ def runner_func(recursive, start):
538582
if recursive:
539583
stackless.tasklet(runner_func)(recursive - 1, start)
540584
with stackless.atomic():
541-
stackless.run(2, soft=soft, totaltimeout=True, ignore_nesting=True)
585+
stackless.run(10000, soft=soft, totaltimeout=True, ignore_nesting=True)
542586
a = self.awoken
543587
self.awoken += 1
544588
if recursive == start:
@@ -568,16 +612,48 @@ def test_watchdog_priority_hard(self):
568612
"""Verify that outermost "real" watchdog gets awoken (hard)"""
569613
self._test_watchdog_priority(False)
570614

615+
def _test_schedule_deeper(self, soft):
616+
# get rid of self.worker
617+
stackless.run()
618+
self.assertFalse(self.worker.alive)
619+
self.assertEqual(self.done, 1)
620+
self.assertListEqual([None], self.watchdog_list)
621+
622+
tasklets = [None, None, None] # watchdog1, watchdog2, worker
623+
624+
def worker():
625+
self.assertListEqual([tasklets[0], stackless.main, tasklets[0], tasklets[1]], self.watchdog_list)
626+
self.assertFalse(tasklets[1].scheduled)
627+
tasklets[1].insert()
628+
self.done += 1
629+
for i in range(100):
630+
for j in range(100):
631+
dummy = i * j
632+
if soft:
633+
stackless.schedule()
634+
self.done += 1
635+
636+
def watchdog2():
637+
tasklets[2] = stackless.tasklet(worker)()
638+
stackless.run()
639+
640+
def watchdog1():
641+
tasklets[1] = stackless.tasklet(watchdog2)()
642+
victim = stackless.run(1000, soft=soft, ignore_nesting=True, totaltimeout=True)
643+
self.assertEqual(self.done, 2, "worker interrupted too early or to late, adapt timeout: %d" % self.done)
644+
if not soft:
645+
self.assertEqual(tasklets[2], victim)
646+
647+
tasklets[0] = stackless.tasklet(watchdog1)()
648+
stackless.run()
649+
self.assertLessEqual([None], self.watchdog_list)
650+
stackless.run()
651+
652+
def test_schedule_deeper_soft(self):
653+
self._test_schedule_deeper(True)
571654

572-
def load_tests(loader, tests, pattern):
573-
"""custom loader to run just a subset"""
574-
suite = unittest.TestSuite()
575-
test_cases = [TestNewWatchdog] # , TestDeadlock]
576-
for test_class in test_cases:
577-
tests = loader.loadTestsFromTestCase(test_class)
578-
suite.addTests(tests)
579-
return suite
580-
del load_tests # disabled
655+
def test_schedule_deeper_hard(self):
656+
self._test_schedule_deeper(False)
581657

582658

583659
if __name__ == '__main__':

0 commit comments

Comments
 (0)