Skip to content

Commit a9f07c8

Browse files
authored
Merge pull request #52 from paulromano/nonblocking-pipe
Avoid hanging by using non-blocking pipe
2 parents 8f9c180 + 739af8b commit a9f07c8

File tree

2 files changed

+41
-8
lines changed

2 files changed

+41
-8
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,29 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1010
### Added
1111

1212
* RELAP-5 Plugin
13+
* MCNP Plugin
14+
* Serpent Plugin
1315

1416
### Changes
1517

1618
* The `Plugin.__call__` method now allows arbitrary keyword arguments to be
1719
passed on to the `Plugin.run` method
1820
* The `Database` class now acts like a sequence
1921
* Database directory names use random strings to avoid clashes when multiple
20-
instances of WATTS are running simulataneously
22+
instances of WATTS are running simultaneously
2123
* File template-based plugins now accept an `extra_template_inputs` argument
2224
indicating extra template files that should be rendered
2325
* The `PluginOpenMC` class now takes an optional `function` argument that
2426
specifies an arbitrary execution sequence
2527
* All plugins consistently use an attribute `executable` for specifying the path
2628
to an executable
2729

30+
### Fixed
31+
32+
* Use non-blocking pipe when capturing output to avoid some plugins stalling.
33+
* Avoid use of Unix-specific features in the Python standard library when
34+
running on Windows
35+
2836
## [0.2.0]
2937

3038
### Added

src/watts/fileutils.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# SPDX-License-Identifier: MIT
33

44
from contextlib import contextmanager
5+
import errno
56
import os
67
import platform
78
import select
@@ -10,6 +11,9 @@
1011
import tempfile
1112
from typing import Union
1213

14+
if sys.platform != 'win32':
15+
import fcntl
16+
1317
# Type for arguments that accept file paths
1418
PathLike = Union[str, bytes, os.PathLike]
1519

@@ -91,18 +95,39 @@ def run(args):
9195
Based on https://stackoverflow.com/a/12272262 and
9296
https://stackoverflow.com/a/7730201
9397
"""
94-
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
95-
universal_newlines=True)
98+
# Windows doesn't support select.select and fcntl module so just default to
99+
# using subprocess.run. In this case, show_stdout/show_stderr won't work.
100+
if sys.platform == 'win32':
101+
subprocess.run(args)
102+
return
103+
104+
# Helper function to add the O_NONBLOCK flag to a file descriptor
105+
def make_async(fd):
106+
fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
107+
108+
# Helper function to read some data from a file descriptor, ignoring EAGAIN errors
109+
def read_async(fd):
110+
try:
111+
return fd.read()
112+
except IOError as e:
113+
if e.errno != errno.EAGAIN:
114+
raise e
115+
else:
116+
return ''
117+
118+
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
119+
make_async(p.stdout)
120+
make_async(p.stderr)
96121

97122
while True:
98-
select.select([p.stdout, p.stderr], [], [])
123+
select.select([p.stdout, p.stderr], [], [], 0)
99124

100-
stdout_data = p.stdout.read()
101-
stderr_data = p.stderr.read()
125+
stdout_data = read_async(p.stdout)
126+
stderr_data = read_async(p.stderr)
102127
if stdout_data:
103-
sys.stdout.write(stdout_data)
128+
sys.stdout.write(stdout_data.decode())
104129
if stderr_data:
105-
sys.stderr.write(stderr_data)
130+
sys.stderr.write(stderr_data.decode())
106131

107132
if p.poll() is not None:
108133
break

0 commit comments

Comments
 (0)