Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deterministic scheduling via Hypothesis #73

Merged
merged 5 commits into from
Feb 12, 2019

Conversation

Zac-HD
Copy link
Member

@Zac-HD Zac-HD commented Jan 6, 2019

Related to HypothesisWorks/hypothesis#1709 and python-trio/trio#239.

And now that HypothesisWorks/hypothesis#1741 has been released, this actually works and is ready to merge 🎉

@codecov
Copy link

codecov bot commented Jan 6, 2019

Codecov Report

Merging #73 into master will increase coverage by 0.22%.
The diff coverage is 100%.

@@            Coverage Diff             @@
##           master      #73      +/-   ##
==========================================
+ Coverage   99.56%   99.78%   +0.22%     
==========================================
  Files          19       19              
  Lines         461      476      +15     
  Branches       43       41       -2     
==========================================
+ Hits          459      475      +16     
+ Misses          2        1       -1
Impacted Files Coverage Δ
pytest_trio/_tests/test_fixture_ordering.py 100% <ø> (ø) ⬆️
pytest_trio/_tests/test_hypothesis_interaction.py 100% <100%> (ø) ⬆️
pytest_trio/plugin.py 99.47% <100%> (+0.53%) ⬆️

@njsmith
Copy link
Member

njsmith commented Jan 8, 2019

The other Trio part is python-trio/trio#840 which is merged and should be released as part of 0.10.0 any moment now.

@Zac-HD Zac-HD force-pushed the hypothesis-determinism branch from 2becc5d to 3775f98 Compare January 22, 2019 21:29
@Zac-HD
Copy link
Member Author

Zac-HD commented Jan 22, 2019

Ping @njsmith, we've just released the Hypothesis end of this 😃

@Zac-HD
Copy link
Member Author

Zac-HD commented Jan 26, 2019

CC @pquentin, @touilleMan ?

@pquentin
Copy link
Member

Sorry, I'm not familiar with Hypothesis. Is there a way to test manually that this integration between pytest-trio and Hypothesis is now working? eg. a test somewhere that is only deterministic after those register_random PRs. Thanks!

@Zac-HD
Copy link
Member Author

Zac-HD commented Jan 27, 2019

Is there a way to test manually that this integration between pytest-trio and Hypothesis is now working? eg. a test somewhere that is only deterministic after those register_random PRs.

Hmm, I don't actually know the Trio internals well enough to say! We would need a test that depends on the ordering of a batch of tasks. Alternatively it would be easy but not that meaningful to test that the random instance in question is in fact seeded by Hypothesis...

@njsmith
Copy link
Member

njsmith commented Jan 27, 2019

I guess what I'd do is write some code like:

async def scheduler_trace():
    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 trace

And then run this code 3-4 times and check that trace comes out the same each time.

@pquentin
Copy link
Member

I did not manage to make this work with hypothesis, and in fact I have not managed to prove that scheduler_trace is deterministic without shuffling. With 0b8f377e8a95103c7b4e4ca675ccb6ce942e897a checked out and shuffling disabled manually, python -R t.py > 1.txt && python -R t.py > 2.txt && diff -u 1.txt 2.txt often outputs a diff. Here's t.py:

import trio


async def scheduler_trace():
    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)

    print(trace)


async def run5():
    for _ in range(5):
        await scheduler_trace()


if __name__ == "__main__":
    print(trio.__version__)
    trio.run(run5)

@njsmith
Copy link
Member

njsmith commented Jan 28, 2019

in fact I have not managed to prove that scheduler_trace is deterministic without shuffling

...huh. I have no idea why that would be. Sounds like you were absolutely right to ask for a test here! I probably would have let it slide, shows what I know...

@njsmith
Copy link
Member

njsmith commented Jan 28, 2019

Oh you know what, I bet it's because when cancel scopes put themselves into the global deadline list, it's sorted by deadline... but if two scopes have the exact same deadline, then id(self) is used as the tie-breaker. (And checkpoint is currently implemented as a cancel scope that expires immediately.) And this doesn't directly explain it, because we currently process all deadlines, and then afterwards schedule all the newly-cancelled tasks. But in the mean time we dump the batch of newly-cancelled tasks into a set, and the set ordering is dependent on both id(task) and the order that the items are inserted. The details of this batching might change as part of python-trio/trio#860, but that won't resolve the issue...

huh, guaranteeing deterministic scheduling in the presence of multiple tasks with identical timeouts seems pretty non-trivial!

@Zac-HD
Copy link
Member Author

Zac-HD commented Jan 28, 2019

It goes deeper - self._tasks is also a set, and thus another source of non-determinism. I suspect that's a fairly deep rabbit-hole, as making CancelScope._tasks a list doesn't change the result.

Unfortunately I don't have the time to take this on at the moment, but I could open an issue to make scheduling deterministic-except-for-shuffling if you agree this would be useful?

@njsmith
Copy link
Member

njsmith commented Jan 28, 2019

I'm a bit wary of adding complexity to the core loop for this, for maintenance and speed reasons, but the use case does make sense, so I guess it depends on how unobtrusive we can make it.

I guess one idea would be to assign each Task an incrementing counter when it's created, and then at scheduling time sort the run queue by this counter before scrambling?

@pquentin
Copy link
Member

OK, trio 0.11.0 is out. Let's cycle this pull request.

@pquentin pquentin closed this Feb 11, 2019
@pquentin pquentin reopened this Feb 11, 2019
@pquentin
Copy link
Member

pquentin commented Feb 11, 2019

@Zac-HD There's a yapf formatting issue left, but otherwise CI is now passing!

Oh, and it would be nice to require trio >= 0.11.0.

@Zac-HD Zac-HD force-pushed the hypothesis-determinism branch from a55596f to 414ec14 Compare February 11, 2019 06:10
@Zac-HD
Copy link
Member Author

Zac-HD commented Feb 11, 2019

And it's passing 🚀:sparkles:

@touilleMan
Copy link
Member

This is reeeally cool, thanks a lot @Zac-HD ;-)

@pquentin pquentin merged commit f20adde into python-trio:master Feb 12, 2019
@pquentin
Copy link
Member

Thank you for your determinism determination! This PR required work on three projects, and you never gave up. :)

@Zac-HD Zac-HD deleted the hypothesis-determinism branch February 12, 2019 05:59
@Zac-HD
Copy link
Member Author

Zac-HD commented Feb 12, 2019

I'm pretty pleased with the end result, but also happy to be finished! It's lovely to work with projects that care so much about both correctness and usability 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants