Depois de entender o processo completo de máquinas em * unix, você encontrará facilmente uma solução mais simples:
Considere este exemplo simples de como tornar o método communic () com tempo limite, usando select.select () (disponível quase em todo lugar no * nix atualmente). Isso também pode ser escrito com epoll / poll / kqueue, mas a variante select.select () pode ser um bom exemplo para você. E as principais limitações do select.select () (speed e 1024 max fds) não são aplicáveis à sua tarefa.
Isso funciona no * nix, não cria threads, não usa sinais, pode ser lançado a partir de qualquer thread (não apenas principal) e rápido o suficiente para ler 250mb / s de dados do stdout na minha máquina (i5 2.3ghz).
Há um problema ao ingressar no stdout / stderr no final da comunicação. Se você tiver uma saída enorme de programa, isso poderá levar ao grande uso de memória. Mas você pode ligar para communic () várias vezes com tempos limite menores.
class Popen(subprocess.Popen):
def communicate(self, input=None, timeout=None):
if timeout is None:
return subprocess.Popen.communicate(self, input)
if self.stdin:
# Flush stdio buffer, this might block if user
# has been writing to .stdin in an uncontrolled
# fashion.
self.stdin.flush()
if not input:
self.stdin.close()
read_set, write_set = [], []
stdout = stderr = None
if self.stdin and input:
write_set.append(self.stdin)
if self.stdout:
read_set.append(self.stdout)
stdout = []
if self.stderr:
read_set.append(self.stderr)
stderr = []
input_offset = 0
deadline = time.time() + timeout
while read_set or write_set:
try:
rlist, wlist, xlist = select.select(read_set, write_set, [], max(0, deadline - time.time()))
except select.error as ex:
if ex.args[0] == errno.EINTR:
continue
raise
if not (rlist or wlist):
# Just break if timeout
# Since we do not close stdout/stderr/stdin, we can call
# communicate() several times reading data by smaller pieces.
break
if self.stdin in wlist:
chunk = input[input_offset:input_offset + subprocess._PIPE_BUF]
try:
bytes_written = os.write(self.stdin.fileno(), chunk)
except OSError as ex:
if ex.errno == errno.EPIPE:
self.stdin.close()
write_set.remove(self.stdin)
else:
raise
else:
input_offset += bytes_written
if input_offset >= len(input):
self.stdin.close()
write_set.remove(self.stdin)
# Read stdout / stderr by 1024 bytes
for fn, tgt in (
(self.stdout, stdout),
(self.stderr, stderr),
):
if fn in rlist:
data = os.read(fn.fileno(), 1024)
if data == '':
fn.close()
read_set.remove(fn)
tgt.append(data)
if stdout is not None:
stdout = ''.join(stdout)
if stderr is not None:
stderr = ''.join(stderr)
return (stdout, stderr)