Skip to content

Commit

Permalink
Support deterministic scheduling
Browse files Browse the repository at this point in the history
Very important for Hypothesis, and arguably for debugging in general.
  • Loading branch information
Zac-HD committed Jan 29, 2019
1 parent 0b8f377 commit b675ef3
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 1 deletion.
4 changes: 4 additions & 0 deletions newsfragments/890.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
The Trio scheduler can now be made deterministic by seeding the internal
random.Random instance that is used to shuffle available tasks.
This is not public API, but is important for some tools such as
pytest-trio's `Hypothesis <https://hypothesis.readthedocs.io>`_ integration.
9 changes: 8 additions & 1 deletion trio/_core/_run.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import functools
import itertools
import logging
import os
import random
Expand Down Expand Up @@ -682,6 +683,7 @@ class Task:
name = attr.ib()
# PEP 567 contextvars context
context = attr.ib()
_counter = attr.ib(init=False, factory=itertools.count().__next__)

# Invariant:
# - for unscheduled tasks, _next_send is None
Expand Down Expand Up @@ -1556,7 +1558,12 @@ def run_impl(runner, async_fn, args):
# scheduling, then there are other things that will probably need to
# change too, like the deadlines tie-breaker and the non-deterministic
# ordering of task._notify_queues.)
batch = list(runner.runq)
#
# We sort tasks before shuffling them so that seeding the Random
# instance can make the scheduler deterministic, which is important
# for testing and debugging, especially with tools such as Hypothesis,
# without giving up the advantages of sets everywhere else.
batch = sorted(runner.runq, key=lambda t: t._counter)
runner.runq.clear()
_r.shuffle(batch)
while batch:
Expand Down
39 changes: 39 additions & 0 deletions trio/tests/test_scheduler_determinism.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import trio


async def scheduler_trace():
"""Returns a scheduler-dependent value we can use to check determinism."""
trace = []

async def tracer(name):
for i in range(10):
trace.append((name, i))
await trio.sleep(0)

async with trio.open_nursery() as nursery:
for i in range(5):
nursery.start_soon(tracer, i)

return tuple(trace)


def test_the_trio_scheduler_is_not_deterministic():
# At least, not yet. See https://github.com/python-trio/trio/issues/32
traces = []
for _ in range(10):
traces.append(trio.run(scheduler_trace))
assert len(set(traces)) == len(traces)


def test_the_trio_scheduler_is_deterministic_if_seeded():
traces = []
for _ in range(10):
state = trio._core._run._r.getstate()
try:
trio._core._run._r.seed(0)
traces.append(trio.run(scheduler_trace))
finally:
trio._core._run._r.setstate(state)

assert len(traces) == 10
assert len(set(traces)) == 1

0 comments on commit b675ef3

Please sign in to comment.