Skip to content

Commit 4907ff3

Browse files
James Tatumjtatum
James Tatum
authored andcommitted
Make waiter cross-platform
Waiter depended on a couple of posix tricks. The first was a clever use of os.waitpid(-1, 0) to wait for any spawned processes to return. On Windows, this raises an exception - Windows has no concept of a process group. The second was the use of NamedTempFile(). On anything but Windows, a file can be open for writing and for reading at the same time. This trick actually isn't necessary the way NamedTempFile is used here. It exposes the actual file object on an attribute named file, and it's already opened in binary mode. Typeshed does not know about this yet, I'll submit a PR to fix the typeshed stub for tempfile.
1 parent 2b33511 commit 4907ff3

File tree

1 file changed

+16
-23
lines changed

1 file changed

+16
-23
lines changed

mypy/waiter.py

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,38 +31,23 @@ def __init__(self, name: str, args: List[str], *, cwd: str = None,
3131
self.end_time = None # type: float
3232

3333
def start(self) -> None:
34-
self.outfile = tempfile.NamedTemporaryFile()
34+
self.outfile = tempfile.TemporaryFile()
3535
self.start_time = time.time()
3636
self.process = Popen(self.args, cwd=self.cwd, env=self.env,
3737
stdout=self.outfile, stderr=STDOUT)
3838
self.pid = self.process.pid
3939

40-
def handle_exit_status(self, status: int) -> None:
41-
"""Update process exit status received via an external os.waitpid() call."""
42-
# Inlined subprocess._handle_exitstatus, it's not a public API.
43-
# TODO(jukka): I'm not quite sure why this is implemented like this.
44-
self.end_time = time.time()
45-
process = self.process
46-
assert process.returncode is None
47-
if os.WIFSIGNALED(status):
48-
process.returncode = -os.WTERMSIG(status)
49-
elif os.WIFEXITED(status):
50-
process.returncode = os.WEXITSTATUS(status)
51-
else:
52-
# Should never happen
53-
raise RuntimeError("Unknown child exit status!")
54-
assert process.returncode is not None
55-
5640
def wait(self) -> int:
5741
return self.process.wait()
5842

5943
def status(self) -> Optional[int]:
6044
return self.process.returncode
6145

6246
def read_output(self) -> str:
63-
with open(self.outfile.name, 'rb') as file:
64-
# Assume it's ascii to avoid unicode headaches (and portability issues).
65-
return file.read().decode('ascii')
47+
file = self.outfile
48+
file.seek(0)
49+
# Assume it's ascii to avoid unicode headaches (and portability issues).
50+
return file.read().decode('ascii')
6651

6752
def cleanup(self) -> None:
6853
self.outfile.close()
@@ -178,17 +163,25 @@ def _record_time(self, name: str, elapsed_time: float) -> None:
178163
name2 = re.sub('( .*?) .*', r'\1', name) # First two words.
179164
self.times2[name2] = elapsed_time + self.times2.get(name2, 0)
180165

166+
def _poll_current(self) -> Tuple[int, int]:
167+
while True:
168+
time.sleep(.25)
169+
for pid in self.current:
170+
cmd = self.current[pid][1]
171+
code = cmd.process.poll()
172+
if code is not None:
173+
cmd.end_time = time.time()
174+
return pid, code
175+
181176
def _wait_next(self) -> Tuple[List[str], int, int]:
182177
"""Wait for a single task to finish.
183178
184179
Return tuple (list of failed tasks, number test cases, number of failed tests).
185180
"""
186-
pid, status = os.waitpid(-1, 0)
181+
pid, status = self._poll_current()
187182
num, cmd = self.current.pop(pid)
188183
name = cmd.name
189184

190-
cmd.handle_exit_status(status)
191-
192185
self._record_time(cmd.name, cmd.elapsed_time)
193186

194187
rc = cmd.wait()

0 commit comments

Comments
 (0)