Commit 50507d2b authored by Xavier Thompson's avatar Xavier Thompson

SlapPopen: Fix select-based timeout reads

Read from stdout or stderr only when the event returned by the
selector guarantees that the read will be non-blocking.

Switch to using high-level selectors API instead of low-level
select/poll.
parent 581b8aa6
......@@ -33,7 +33,7 @@ import hashlib
import os
import pkg_resources
import pwd
import select
import selectors
import stat
import sys
import logging
......@@ -155,49 +155,46 @@ class SlapPopen(subprocess.Popen):
self.stdin.close()
self.stdin = None
stderr_fileno = stdout_fileno = None
buffers = {}
if kwargs['stdout'] is subprocess.PIPE:
line_logger = LineLogger(logger)
stdout_fileno = self.stdout.fileno()
buffers[stdout_fileno] = []
buffers[self.stdout] = []
if kwargs['stderr'] is subprocess.PIPE:
stderr_fileno = self.stderr.fileno()
buffers[stderr_fileno] = []
poll = select.poll()
for fd in buffers:
poll.register(fd)
active = len(buffers)
if timeout is not None:
deadline = time.time() + timeout
while active:
for fd, _ in poll.poll(timeout):
data = os.read(fd, 4096).decode('utf-8', 'replace')
if data:
if fd == stdout_fileno:
line_logger.update(data)
buffers[fd].append(data)
else:
if fd == stdout_fileno:
line_logger.flush()
poll.unregister(fd)
active -= 1
if timeout is not None:
timeout = deadline - time.time()
if timeout <= 0:
timeout = 0
break
buffers[self.stderr] = []
try:
self.wait(timeout=timeout)
with selectors.DefaultSelector() as selector:
for fileobj in buffers:
selector.register(fileobj, selectors.EVENT_READ)
if timeout is not None:
deadline = time.time() + timeout
while selector.get_map():
for key, _ in selector.select(timeout):
data = os.read(key.fd, 4096).decode('utf-8', 'replace')
if data:
if key.fileobj == self.stdout:
line_logger.update(data)
buffers[key.fileobj].append(data)
else:
if key.fileobj == self.stdout:
line_logger.flush()
selector.unregister(key.fileobj)
key.fileobj.close()
if timeout is not None:
timeout = deadline - time.time()
if timeout <= 0:
timeout = 0
break
self.wait(timeout=timeout)
except subprocess.TimeoutExpired as e:
for p in killProcessTree(self.pid, logger):
p.wait(timeout=10) # arbitrary timeout, wait until process is killed
self.poll() # set returncode (and avoid still-running warning)
e.output = e.stdout = ''.join(buffers.get(stdout_fileno, ()))
e.stderr = ''.join(buffers.get(stderr_fileno, ()))
e.output = e.stdout = ''.join(buffers.get(self.stdout, ()))
e.stderr = ''.join(buffers.get(self.stderr, ()))
raise
finally:
for s in (self.stdout, self.stderr):
......@@ -207,8 +204,8 @@ class SlapPopen(subprocess.Popen):
except OSError:
pass
self.output = ''.join(buffers.get(stdout_fileno, ()))
self.error = ''.join(buffers.get(stderr_fileno, ()))
self.output = ''.join(buffers.get(self.stdout, ()))
self.error = ''.join(buffers.get(self.stderr, ()))
def md5digest(url):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment