Skip to content

Fix deadlock with --parallel and lots of output #1202

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 1 commit into from
Mar 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog/1183.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix deadlock when using ``--parallel`` and having environments with lots of output - by :user:`asottile`.
25 changes: 18 additions & 7 deletions src/tox/session/commands/run/parallel.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import os
import subprocess
import sys
import tempfile
Copy link
Member

Choose a reason for hiding this comment

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

I believe this is already implemented partially within Action, we just need to make this an Action and perhaps have a flag allowing this👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is way way simpler than involving Action

Copy link
Member

Choose a reason for hiding this comment

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

I know it's simpler but not using the Action is a bug, as generally, that's what we should use. That was added especially for such use cases, and somewhere we even have an issue to make this content available as a file afterwards (for CIs); so that's the right solution to solve both at the same time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that seems orthogonal and existing :P

Copy link
Member

Choose a reason for hiding this comment

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

not really orthogonal given in sense that the buffer overload is what is breaking here things, and would we write to file that would not exist 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's not that simple, no -- tox -p is implemented by running sub process which invoke python -m tox: https://github.com/tox-dev/tox/pull/1202/files#diff-7f1e93c77a2f9f1b1f2c01a8950bad0dR25

Copy link
Member

Choose a reason for hiding this comment

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

I know, I did it, bur action also uses underneath subprocess via popen.

Copy link
Member

Choose a reason for hiding this comment

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

Anyways we can go ahead just mind you we'll need it switch at some point. Thought if not too hard we could do it now. If you find it non trivial we can hold for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

my initial try ended in a different deadlock due to the log files, I'd rather not

Copy link
Member

Choose a reason for hiding this comment

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

let's come back to this later then 👍

from collections import OrderedDict
from threading import Event, Semaphore, Thread

from tox import reporter
from tox.config.parallel import ENV_VAR_KEY as PARALLEL_ENV_VAR_KEY
from tox.util.spinner import Spinner

if sys.version_info >= (3, 7):
from contextlib import nullcontext
else:
import contextlib

@contextlib.contextmanager
def nullcontext(enter_result=None):
yield enter_result


def run_parallel(config, venv_dict):
"""here we'll just start parallel sub-processes"""
Expand All @@ -23,10 +33,12 @@ def run_parallel(config, venv_dict):
max_parallel = len(venv_dict)
semaphore = Semaphore(max_parallel)
finished = Event()
sink = None if live_out else subprocess.PIPE

ctx = nullcontext if live_out else tempfile.NamedTemporaryFile
stderr = None if live_out else subprocess.STDOUT

show_progress = not live_out and reporter.verbosity() > reporter.Verbosity.QUIET
with Spinner(enabled=show_progress) as spinner:
with Spinner(enabled=show_progress) as spinner, ctx() as sink:

def run_in_thread(tox_env, os_env):
res = None
Expand All @@ -41,7 +53,7 @@ def run_in_thread(tox_env, os_env):
args_sub,
env=os_env,
stdout=sink,
stderr=sink,
stderr=stderr,
stdin=None,
universal_newlines=True,
)
Expand All @@ -63,16 +75,15 @@ def run_in_thread(tox_env, os_env):
outcome(env_name)

if not live_out:
out, err = process.communicate()
sink.seek(0)
out = sink.read().decode("UTF-8", errors="replace")
if res or tox_env.envconfig.parallel_show_output:
outcome = (
"Failed {} under process {}, stdout:\n".format(env_name, process.pid)
if res
else ""
)
message = "{}{}{}".format(
outcome, out, "\nstderr:\n{}".format(err) if err else ""
).rstrip()
message = "{}{}".format(outcome, out).rstrip()
reporter.quiet(message)

threads = []
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/session/test_parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,22 @@ def test_parallel_error_report(cmd, initproj):
assert len(summary_lines) == 1, msg

assert result.outlines[summary_lines[0] + 1 :] == ["ERROR: a: parallel child exit code 1"]


def test_parallel_deadlock(cmd, initproj):
tox_ini = """\
[tox]
envlist = e1,e2
skipsdist = true

[testenv:e1]
commands =
python -c '[print("hello world") for _ in range(5000)]'

[testenv:e2]
commands =
python -c '[print("hello world") for _ in range(5000)]'
"""

initproj("pkg123-0.7", filedefs={"tox.ini": tox_ini})
cmd("-p", "2") # used to hang indefinitely