Attempt to avoid the hang condition

The Python 2.7 subprocess module does not promise thread safety.
This commit is contained in:
Jean-Paul Calderone 2019-04-01 12:54:51 -04:00
parent cd16b924e2
commit 7b314ceab8
2 changed files with 94 additions and 12 deletions

View File

@ -0,0 +1,74 @@
"""
A copy of the implementation of Twisted's ``getProcessOutputAndValue``
with the fix for Twisted #9607 (support for stdinBytes) patched in.
"""
from __future__ import (
division,
absolute_import,
print_function,
unicode_literals,
)
from io import BytesIO
from twisted.internet import protocol, defer
class _EverythingGetter(protocol.ProcessProtocol):
def __init__(self, deferred, stdinBytes=None):
self.deferred = deferred
self.outBuf = BytesIO()
self.errBuf = BytesIO()
self.outReceived = self.outBuf.write
self.errReceived = self.errBuf.write
self.stdinBytes = stdinBytes
def connectionMade(self):
if self.stdinBytes is not None:
self.transport.writeToChild(0, self.stdinBytes)
# The only compelling reason not to _always_ close stdin here is
# backwards compatibility.
self.transport.closeStdin()
def processEnded(self, reason):
out = self.outBuf.getvalue()
err = self.errBuf.getvalue()
e = reason.value
code = e.exitCode
if e.signal:
self.deferred.errback((out, err, e.signal))
else:
self.deferred.callback((out, err, code))
def _callProtocolWithDeferred(protocol, executable, args, env, path,
reactor=None, protoArgs=()):
if reactor is None:
from twisted.internet import reactor
d = defer.Deferred()
p = protocol(d, *protoArgs)
reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path)
return d
def getProcessOutputAndValue(executable, args=(), env={}, path=None,
reactor=None, stdinBytes=None):
"""Spawn a process and returns a Deferred that will be called back with
its output (from stdout and stderr) and it's exit code as (out, err, code)
If a signal is raised, the Deferred will errback with the stdout and
stderr up to that point, along with the signal, as (out, err, signalNum)
"""
return _callProtocolWithDeferred(
_EverythingGetter,
executable,
args,
env,
path,
reactor,
protoArgs=(stdinBytes,),
)

View File

@ -1,9 +1,13 @@
import os.path, re, sys, subprocess
from __future__ import (
absolute_import,
)
import os.path, re, sys
from twisted.trial import unittest
from twisted.python import usage, runtime
from twisted.internet import threads
from twisted.internet.defer import inlineCallbacks, returnValue
from allmydata.util import fileutil, pollmixin
@ -15,6 +19,9 @@ import allmydata
from allmydata import __appname__
from .common_util import parse_cli, run_cli
from ._twisted_9607 import (
getProcessOutputAndValue,
)
timeout = 240
@ -52,18 +59,19 @@ class RunBinTahoeMixin:
returnValue(tahoe_pieces[-1].strip("()"))
def run_bintahoe(self, args, stdin=None, python_options=[], env=None):
command = [sys.executable] + python_options + ["-m", "allmydata.scripts.runner"] + args
command = sys.executable
argv = python_options + ["-m", "allmydata.scripts.runner"] + args
if stdin is None:
stdin_stream = None
else:
stdin_stream = subprocess.PIPE
if env is None:
env = os.environ
def _run():
p = subprocess.Popen(command, stdin=stdin_stream, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
(out, err) = p.communicate(stdin)
return (out, err, p.returncode)
return threads.deferToThread(_run)
d = getProcessOutputAndValue(command, argv, env, stdinBytes=stdin)
def fix_signal(result):
# Mirror subprocess.Popen.returncode structure
(out, err, signal) = result
return (out, err, -signal)
d.addErrback(fix_signal)
return d
class BinTahoe(common_util.SignalMixin, unittest.TestCase, RunBinTahoeMixin):