Skip to content

Implement capacity limitation for run_in_worker_thread #181

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

Merged
merged 8 commits into from
Jun 26, 2017

Conversation

njsmith
Copy link
Member

@njsmith njsmith commented May 29, 2017

You can pass a Semaphore here to avoid overwhelming the system with
too many simultaneous threads.

@codecov
Copy link

codecov bot commented Jun 18, 2017

Codecov Report

Merging #181 into master will increase coverage by 0.05%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #181      +/-   ##
==========================================
+ Coverage   99.06%   99.11%   +0.05%     
==========================================
  Files          63       63              
  Lines        8420     8745     +325     
  Branches      609      629      +20     
==========================================
+ Hits         8341     8668     +327     
+ Misses         62       61       -1     
+ Partials       17       16       -1
Impacted Files Coverage Δ
trio/_threads.py 100% <100%> (ø) ⬆️
trio/tests/test_threads.py 100% <100%> (ø) ⬆️
trio/_sync.py 100% <100%> (ø) ⬆️
trio/tests/test_sync.py 100% <100%> (ø) ⬆️
trio/tests/test_util.py 100% <0%> (ø) ⬆️
trio/_util.py 90% <0%> (+2.35%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6311069...9f21a04. Read the comment docs.

- New synchronization primitive: CapacityLimiter. Like a Semaphore but
  more specialized. See python-triogh-182 for rationale.

- Add limiter= argument to run_in_worker_thread, that allows one to
  correctly (modulo
  python-trio#6 (comment))
  control the number of active threads.

- Added new function current_default_worker_thread_limiter(), which
  creates or returns a run-local CapacityLimiter, and made
  run_in_worker_thread use it by default when no other limiter= is
  given.

Closes: python-triogh-10, python-triogh-57, python-triogh-156
@njsmith njsmith changed the title Add limiter= argument to run_in_worker_thread Implement capacity limitation for run_in_worker_thread Jun 18, 2017
@njsmith
Copy link
Member Author

njsmith commented Jun 18, 2017

I just pushed a new, much-expanded version of this that I think finishes solving the basic don't-run-too-many-threads-at-once problem, and I think it's ready for review if anyone wants to take a look.

kyrias
kyrias previously requested changes Jun 18, 2017
Copy link
Contributor

@kyrias kyrias left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, and probably more useful than the old version!

trio/_sync.py Outdated

"""
if borrower in self._borrowers:
raise RuntimeError(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this also be in the docstring?

trio/_threads.py Outdated
def report_back_in_trio_thread_fn(result):
print("in trio thread", result)
def do_release_then_return_result():
print("asdF")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

;)

@njsmith
Copy link
Member Author

njsmith commented Jun 22, 2017

@kyrias: Addressed those issues, and also added some docs and a few more tests.

Let's see what Travis makes of this. The last run where everything froze on PyPy makes me very nervous. I can't reproduce it locally, but it seems too much too be a coincidence that both PyPy runs froze inside the thread tests on a PR dealing with the thread code...

@njsmith
Copy link
Member Author

njsmith commented Jun 22, 2017

Welp, this appears to be a PyPy bug: https://bitbucket.org/pypy/pypy/issues/2591/closed-over-variable-updates-made-in-one

I think we might need to drop PyPy support in our CI until this gets fixed. But for now I'm going to bed, and we'll see if @arigo has magically fixed it by the time I wake up :-)

@Carreau
Copy link
Contributor

Carreau commented Jun 22, 2017 via email

This is bad. The test is totally valid, and we should be running it on
PyPy. But it's hitting this PyPy bug:

   https://bitbucket.org/pypy/pypy/issues/2591/

This commit should be reverted after PyPy fixes that bug.
@njsmith
Copy link
Member Author

njsmith commented Jun 23, 2017

I just pushed a commit that skips the test for now on PyPy. I'm not super happy about this, because the test is valid and important. But this is an important feature that I don't want to get blocked by PyPy, and this has a few advantages over allow-failures:

  • With allow-failures, no-one will notice if other stuff breaks on pypy
  • In fact, probably no-one will ever look at the pypy results at all, because when a PR is green then who clicks on the detailed results?
  • And the pypy bug was causing freezes and then timing out after 10 minutes, which substantially increases travis's total turnaround time.
  • I version-gated the skip logic, so even if we forget about it then eventually a new version of pypy will come along and start running the test again

@njsmith njsmith mentioned this pull request Jun 23, 2017
@njsmith
Copy link
Member Author

njsmith commented Jun 24, 2017

Here's an exciting plot twist: the pypy bug where local variable assignments can just disappear, also affects all versions of CPython, probably back to 2.1 when closures were introduced! bpo-30744

It just happens that we don't trigger it because one of the required conditions is the presence of a Python-level tracing function, which on PyPy is provided by coverage.py, but on CPython coverage.py has a C accelerator module.

njsmith added 2 commits June 24, 2017 02:51
Basically using nonlocal and threads together is super dangerous.

- There was a weird/harmless one in _await_in_trio_thread, which
  caused me to clean up this code a bit. (It's better to use
  disable_ki_protection as a real decorator, in case it switches to
  mutating its argument in the future...)

- Since test_run_in_worker_thread_limiter is actually doing something
  that's dangerous on both CPython and PyPy and the upstream fix is
  not obvious, let's just avoid nonlocal entirely.
def unprotected_fn():
return fn(*args)
res = _core.Result.capture(unprotected_fn)
print(res)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would make sense to add some off-by-default logging to various parts of trio, hooking into the logging module or somesuch. Might help with debugging things in general.

@kyrias
Copy link
Contributor

kyrias commented Jun 26, 2017

Wowza, that's pretty nasty behavior indeed. AFAICT the code does look sane at least, though I haven't actually played with it yet.

@njsmith
Copy link
Member Author

njsmith commented Jun 26, 2017

I'm going to call that a review and merge this. This has been way more of a roller coaster than I expected and I want to get it landed before another surprise pops up :-).

(Note for posterity: since it turns out that the test that broke PyPy is also fragile on all versions of CPython, I rewrote it to avoid using nonlocal and then reenabled it everywhere.)

@njsmith njsmith merged commit 1d0a764 into python-trio:master Jun 26, 2017
@njsmith njsmith deleted the thread-limiter branch August 8, 2017 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants