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

Possible race-condition while writing bytecode when rewriting assertions #3008

Closed
nicoddemus opened this issue Dec 7, 2017 · 5 comments
Closed
Assignees
Labels
platform: windows windows platform-specific problem topic: rewrite related to the assertion rewrite mechanism type: bug problem that needs to be addressed

Comments

@nicoddemus
Copy link
Member

Since we started porting to Python 3, our builds on Windows fail sometimes, say 5-10% of the runs, in a bizarre and random manner:

___________ ERROR at setup of testMeshAssemblyPlaneControllerNoBBox ___________
[gw3] win32 -- Python 3.5.3 W:\Miniconda\envs\_sci20-win64g-py35\python.exe
file K:\etk\sci20\source\python\sci20\app\mesh_assembly\plane\_tests\test_mesh_assembly_plane_controller.py, line 83
  def testMeshAssemblyPlaneControllerNoBBox(almost_equal):
E       fixture 'py5' not found

Strangely the "name" being reported is always py and some digits, like py2, py7 or py44.

This always happens when running with xdist and in a random test or contest file, which suggest that the assertion rewriting code might some bug in its locking code when writing the rewritten bytecode to the disk, corrupting it.

@nicoddemus nicoddemus added platform: windows windows platform-specific problem topic: rewrite related to the assertion rewrite mechanism type: bug problem that needs to be addressed labels Dec 7, 2017
@nicoddemus nicoddemus self-assigned this Dec 7, 2017
@nicoddemus
Copy link
Member Author

Also, it seems it always changes an identifier to something else, rather than corrupting actual code.

@nicoddemus
Copy link
Member Author

nicoddemus commented Apr 11, 2018

The code responsible for writing the pyc files is this:

def _make_rewritten_pyc(state, source_stat, pyc, co):
"""Try to dump rewritten code to *pyc*."""
if sys.platform.startswith("win"):
# Windows grants exclusive access to open files and doesn't have atomic
# rename, so just write into the final file.
_write_pyc(state, co, source_stat, pyc)
else:
# When not on windows, assume rename is atomic. Dump the code object
# into a file specific to this process and atomically replace it.
proc_pyc = pyc + "." + str(os.getpid())
if _write_pyc(state, co, source_stat, proc_pyc):
os.rename(proc_pyc, pyc)

Unfortunately the comment about Windows is not correct, there's no exclusive access guarantee and it is possible for concurrent processes/threads to write to a file at the same time and possibly corrupt it.

This short script demonstrates this:

import os
import tempfile
import threading

with tempfile.TemporaryDirectory() as tmpdir:
    filename = os.path.join(tmpdir, 'somefile.txt')
    print(f'FILE: {filename}')

    COUNT = 10

    data = [1025 * 1024 * str(i) for i in range(COUNT)]


    def write(i):
        with open(filename, 'w') as f:
            f.write(data[i])

    threads = [threading.Thread(target=write, args=(i,)) for i in range(COUNT)]
    for t in threads:
        t.start()

    for t in threads:
        t.join()

    print('DONE')
    
    with open(filename) as f:
        read_data = set(f.read())

    print(f'READ: {read_data}')

It spawns a number of threads, all of them writing different data to the same file. At the end, it counts how many different characters were written to the file.

Most of the time the output is this:

FILE: C:\Users\Bruno\AppData\Local\Temp\tmpzzgwfa99\somefile.txt
DONE
READ: {'2'}

But sometimes (2/9 on my machine) the file ends up corrupted:

FILE: C:\Users\Bruno\AppData\Local\Temp\tmpm0o7bpuq\somefile.txt
DONE
READ: {'8', '9'}

This behavior is consistent with the CI failures we have seen: corrupted .pyc files around 2-3% of the time.

I searched for some solutions about the problem of atomically renaming a file on multiple platforms and the best library I found was python-atomicwrites, which is small and does just this job.

nicoddemus added a commit to nicoddemus/pytest that referenced this issue Apr 11, 2018
This uses of the `atomicwrites` library.

This is very hard to create a reliable test for.

Fix pytest-dev#3008
nicoddemus added a commit to nicoddemus/pytest that referenced this issue Apr 11, 2018
This uses of the `atomicwrites` library.

This is very hard to create a reliable test for.

Fix pytest-dev#3008
nicoddemus added a commit to nicoddemus/pytest that referenced this issue Apr 12, 2018
This uses of the `atomicwrites` library.

This is very hard to create a reliable test for.

Fix pytest-dev#3008
nicoddemus added a commit to nicoddemus/pytest that referenced this issue Apr 12, 2018
This uses of the `atomicwrites` library.

This is very hard to create a reliable test for.

Fix pytest-dev#3008
@nicoddemus
Copy link
Member Author

Hopefully fixed in #3390. Will re-open if I see this problem again in pytest>=3.6.

@maiksensi
Copy link
Contributor

maiksensi commented Apr 12, 2018

great job for debugging this. Can't actually 👍 enough! Thanks!

@nicoddemus
Copy link
Member Author

@maiksensi were you experiencing this as well?

guykisel referenced this issue in guykisel/inline-plz Jul 6, 2018
This PR updates [pytest](https://pypi.org/project/pytest) from **3.5.1** to **3.6.3**.



<details>
  <summary>Changelog</summary>
  
  
   ### 3.6.2
   ```
   =========================

Bug Fixes
---------

- Fix regression in ``Node.add_marker`` by extracting the mark object of a
  ``MarkDecorator``. (`3555
  &lt;https://github.com/pytest-dev/pytest/issues/3555&gt;`_)

- Warnings without ``location`` were reported as ``None``. This is corrected to
  now report ``&lt;undetermined location&gt;``. (`3563
  &lt;https://github.com/pytest-dev/pytest/issues/3563&gt;`_)

- Continue to call finalizers in the stack when a finalizer in a former scope
  raises an exception. (`3569
  &lt;https://github.com/pytest-dev/pytest/issues/3569&gt;`_)

- Fix encoding error with `print` statements in doctests (`3583
  &lt;https://github.com/pytest-dev/pytest/issues/3583&gt;`_)


Improved Documentation
----------------------

- Add documentation for the ``--strict`` flag. (`3549
  &lt;https://github.com/pytest-dev/pytest/issues/3549&gt;`_)


Trivial/Internal Changes
------------------------

- Update old quotation style to parens in fixture.rst documentation. (`3525
  &lt;https://github.com/pytest-dev/pytest/issues/3525&gt;`_)

- Improve display of hint about ``--fulltrace`` with ``KeyboardInterrupt``.
  (`3545 &lt;https://github.com/pytest-dev/pytest/issues/3545&gt;`_)

- pytest&#39;s testsuite is no longer runnable through ``python setup.py test`` --
  instead invoke ``pytest`` or ``tox`` directly. (`3552
  &lt;https://github.com/pytest-dev/pytest/issues/3552&gt;`_)

- Fix typo in documentation (`3567
  &lt;https://github.com/pytest-dev/pytest/issues/3567&gt;`_)
   ```
   
  
  
   ### 3.6.1
   ```
   =========================

Bug Fixes
---------

- Fixed a bug where stdout and stderr were logged twice by junitxml when a test
  was marked xfail. (`3491
  &lt;https://github.com/pytest-dev/pytest/issues/3491&gt;`_)

- Fix ``usefixtures`` mark applyed to unittest tests by correctly instantiating
  ``FixtureInfo``. (`3498
  &lt;https://github.com/pytest-dev/pytest/issues/3498&gt;`_)

- Fix assertion rewriter compatibility with libraries that monkey patch
  ``file`` objects. (`3503
  &lt;https://github.com/pytest-dev/pytest/issues/3503&gt;`_)


Improved Documentation
----------------------

- Added a section on how to use fixtures as factories to the fixture
  documentation. (`3461 &lt;https://github.com/pytest-dev/pytest/issues/3461&gt;`_)


Trivial/Internal Changes
------------------------

- Enable caching for pip/pre-commit in order to reduce build time on
  travis/appveyor. (`3502
  &lt;https://github.com/pytest-dev/pytest/issues/3502&gt;`_)

- Switch pytest to the src/ layout as we already suggested it for good practice
  - now we implement it as well. (`3513
  &lt;https://github.com/pytest-dev/pytest/issues/3513&gt;`_)

- Fix if in tests to support 3.7.0b5, where a docstring handling in AST got
  reverted. (`3530 &lt;https://github.com/pytest-dev/pytest/issues/3530&gt;`_)

- Remove some python2.5 compatibility code. (`3529
  &lt;https://github.com/pytest-dev/pytest/issues/3529&gt;`_)
   ```
   
  
  
   ### 3.6.0
   ```
   =========================

Features
--------

- Revamp the internals of the ``pytest.mark`` implementation with correct per
  node handling which fixes a number of long standing bugs caused by the old
  design. This introduces new ``Node.iter_markers(name)`` and
  ``Node.get_closest_mark(name)`` APIs. Users are **strongly encouraged** to
  read the `reasons for the revamp in the docs
  &lt;https://docs.pytest.org/en/latest/mark.htmlmarker-revamp-and-iteration&gt;`_,
  or jump over to details about `updating existing code to use the new APIs
  &lt;https://docs.pytest.org/en/latest/mark.htmlupdating-code&gt;`_. (`3317
  &lt;https://github.com/pytest-dev/pytest/issues/3317&gt;`_)

- Now when ``pytest.fixture`` is applied more than once to the same function a
  ``ValueError`` is raised. This buggy behavior would cause surprising problems
  and if was working for a test suite it was mostly by accident. (`2334
  &lt;https://github.com/pytest-dev/pytest/issues/2334&gt;`_)

- Support for Python 3.7&#39;s builtin ``breakpoint()`` method, see `Using the
  builtin breakpoint function
  &lt;https://docs.pytest.org/en/latest/usage.htmlbreakpoint-builtin&gt;`_ for
  details. (`3180 &lt;https://github.com/pytest-dev/pytest/issues/3180&gt;`_)

- ``monkeypatch`` now supports a ``context()`` function which acts as a context
  manager which undoes all patching done within the ``with`` block. (`3290
  &lt;https://github.com/pytest-dev/pytest/issues/3290&gt;`_)

- The ``--pdb`` option now causes KeyboardInterrupt to enter the debugger,
  instead of stopping the test session. On python 2.7, hitting CTRL+C again
  exits the debugger. On python 3.2 and higher, use CTRL+D. (`3299
  &lt;https://github.com/pytest-dev/pytest/issues/3299&gt;`_)

- pytest not longer changes the log level of the root logger when the
  ``log-level`` parameter has greater numeric value than that of the level of
  the root logger, which makes it play better with custom logging configuration
  in user code. (`3307 &lt;https://github.com/pytest-dev/pytest/issues/3307&gt;`_)


Bug Fixes
---------

- A rare race-condition which might result in corrupted ``.pyc`` files on
  Windows has been hopefully solved. (`3008
  &lt;https://github.com/pytest-dev/pytest/issues/3008&gt;`_)

- Also use iter_marker for discovering the marks applying for marker
  expressions from the cli to avoid the bad data from the legacy mark storage.
  (`3441 &lt;https://github.com/pytest-dev/pytest/issues/3441&gt;`_)

- When showing diffs of failed assertions where the contents contain only
  whitespace, escape them using ``repr()`` first to make it easy to spot the
  differences. (`3443 &lt;https://github.com/pytest-dev/pytest/issues/3443&gt;`_)


Improved Documentation
----------------------

- Change documentation copyright year to a range which auto-updates itself each
  time it is published. (`3303
  &lt;https://github.com/pytest-dev/pytest/issues/3303&gt;`_)


Trivial/Internal Changes
------------------------

- ``pytest`` now depends on the `python-atomicwrites
  &lt;https://github.com/untitaker/python-atomicwrites&gt;`_ library. (`3008
  &lt;https://github.com/pytest-dev/pytest/issues/3008&gt;`_)

- Update all pypi.python.org URLs to pypi.org. (`3431
  &lt;https://github.com/pytest-dev/pytest/issues/3431&gt;`_)

- Detect `pytest_` prefixed hooks using the internal plugin manager since
  ``pluggy`` is deprecating the ``implprefix`` argument to ``PluginManager``.
  (`3487 &lt;https://github.com/pytest-dev/pytest/issues/3487&gt;`_)

- Import ``Mapping`` and ``Sequence`` from ``_pytest.compat`` instead of
  directly from ``collections`` in ``python_api.py::approx``. Add ``Mapping``
  to ``_pytest.compat``, import it from ``collections`` on python 2, but from
  ``collections.abc`` on Python 3 to avoid a ``DeprecationWarning`` on Python
  3.7 or newer. (`3497 &lt;https://github.com/pytest-dev/pytest/issues/3497&gt;`_)
   ```
   
  
</details>


 

<details>
  <summary>Links</summary>
  
  - PyPI: https://pypi.org/project/pytest
  - Changelog: https://pyup.io/changelogs/pytest/
  - Repo: https://github.com/pytest-dev/pytest/issues
  - Homepage: http://pytest.org
</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
platform: windows windows platform-specific problem topic: rewrite related to the assertion rewrite mechanism type: bug problem that needs to be addressed
Projects
None yet
Development

No branches or pull requests

2 participants