[pypy-issue] Issue #2591: Closed-over variable updates made in one thread are not always visible in another (pypy/pypy)

Nathaniel Smith issues-reply at bitbucket.org
Thu Jun 22 05:30:46 EDT 2017


New issue 2591: Closed-over variable updates made in one thread are not always visible in another
https://bitbucket.org/pypy/pypy/issues/2591/closed-over-variable-updates-made-in-one

Nathaniel Smith:

Unfortunately, I don't have a minimal reproducer here. But in the trio test suite, I have [some code](https://github.com/njsmith/trio/blob/29adfee7f2a041427eef80b1e30d3d2f77fd488c/trio/tests/test_threads.py#L282-L295) that schematically looks like:

```python
def outer_scope():
    lock = threading.Lock()
    ran = 0
    def thread_fn():
        nonlocal ran
        with lock:
            print("got lock; on entry ran is", ran)
            ran += 1
            print("releasing lock; on exit ran is", ran)
    # ... spawn a bunch of threads running thread_fn()
    # after they all exit, assert ran == thread_count
    # ... it doesn't
```

What's going on? Well, here's output from an actual run of the code I linked above:
```
getting lock
got lock, old values: 0 0 0 0
releasing lock, new values: 1 1 1 1
gate.wait()
thread_fn start
run_thread finished, cancelled: True
getting lock
got lock, old values: 1 1 1 1
releasing lock, new values: 2 2 2 2
gate.wait()
thread_fn start
getting lock
got lock, old values: 1 1 1 1
releasing lock, new values: 2 2 2 2
gate.wait()
run_thread finished, cancelled: True
thread_fn start
run_thread finished, cancelled: True
getting lock
got lock, old values: 2 2 2 2
releasing lock, new values: 3 3 3 3
```

Notice that one thread took the lock, saw the values 1 1 1 1, updated them to 2 2 2 2, and then released the lock; then the next thread took the lock and... saw the values 1 1 1 1 again (!!!??!!). So if I start 10 threads, the final value ends up being 9, because one of the updates was lost.

This was observed with both PyPy3 5.8.0 and with the latest PyPy35 nightly, on Linux x86-64. 

Here's my best attempt at a reproduction recipe:

1. Download Squeaky's PyPy3 5.8.0 build
2. `pip install pytest pytest-cov ipython pyOpenSSL attrs sortedcontainers async_generator idna`
3. `git clone https://github.com/njsmith/trio`
4. `cd trio`
5. `git checkout 29adfee7f2a041427eef80b1e30d3d2f77fd488c`
6. `pytest -W error -ra -k run_in_worker_thread trio -v --cov=trio -s` (you might have to repeat this a few times)

Note: running with coverage enabled, `--cov=trio`, appears to be necessary to hit the bug. If I disable coverage then I haven't managed to reproduce it at all; with coverage enabled it happens more often than not when running locally, and apparently 100% of the time when running on travis.

Here are two Travis logs showing the problem: [log 1](https://travis-ci.org/python-trio/trio/jobs/245702941), [log 2](https://travis-ci.org/python-trio/trio/jobs/245702942)
The logs also show exactly what commands are being run, so might also be useful for reproduction.




More information about the pypy-issue mailing list