Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid hanging by using non-blocking pipe #52

Merged
merged 3 commits into from
Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,29 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added

* RELAP-5 Plugin
* MCNP Plugin
* Serpent Plugin

### Changes

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

### Fixed

* Use non-blocking pipe when capturing output to avoid some plugins stalling.
* Avoid use of Unix-specific features in the Python standard library when
running on Windows

## [0.2.0]

### Added
Expand Down
39 changes: 32 additions & 7 deletions src/watts/fileutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-License-Identifier: MIT

from contextlib import contextmanager
import errno
import os
import platform
import select
Expand All @@ -10,6 +11,9 @@
import tempfile
from typing import Union

if sys.platform != 'win32':
import fcntl

# Type for arguments that accept file paths
PathLike = Union[str, bytes, os.PathLike]

Expand Down Expand Up @@ -91,18 +95,39 @@ def run(args):
Based on https://stackoverflow.com/a/12272262 and
https://stackoverflow.com/a/7730201
"""
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
# Windows doesn't support select.select and fcntl module so just default to
# using subprocess.run. In this case, show_stdout/show_stderr won't work.
if sys.platform == 'win32':
subprocess.run(args)
return

# Helper function to add the O_NONBLOCK flag to a file descriptor
def make_async(fd):
fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)

# Helper function to read some data from a file descriptor, ignoring EAGAIN errors
def read_async(fd):
try:
return fd.read()
except IOError as e:
if e.errno != errno.EAGAIN:
raise e
else:
return ''

p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
make_async(p.stdout)
make_async(p.stderr)

while True:
select.select([p.stdout, p.stderr], [], [])
select.select([p.stdout, p.stderr], [], [], 0)

stdout_data = p.stdout.read()
stderr_data = p.stderr.read()
stdout_data = read_async(p.stdout)
stderr_data = read_async(p.stderr)
if stdout_data:
sys.stdout.write(stdout_data)
sys.stdout.write(stdout_data.decode())
if stderr_data:
sys.stderr.write(stderr_data)
sys.stderr.write(stderr_data.decode())

if p.poll() is not None:
break