Skip to content

Commit

Permalink
Fix resource leak by closing opened file handles (#121)
Browse files Browse the repository at this point in the history
* use already existing reference to file stream instead of creating other

* add changelog

* use separate file handles for skiping process log blocks and seeking patterns

* add py3.10 to acitons workflow

* add py3.10 to list of supported py versions
  • Loading branch information
northernSage committed Dec 31, 2022
1 parent 5e33cbf commit 167f269
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 15 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ jobs:
strategy:
fail-fast: false
matrix:
python: ["3.7", "3.8", "3.9"]
python: ["3.7", "3.8", "3.9", "3.10"]
os: [ubuntu-latest, windows-latest, macos-latest]
include:
- python: "3.10"
tox_env: "py310"
- python: "3.7"
tox_env: "py37"
- python: "3.8"
Expand Down
5 changes: 3 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Upcoming
--------
0.22.0 (UNRELEASED)
-------------------

- Fix resource warnings due to leaked internal file handles (`#121 <https://github.com/pytest-dev/pytest-xprocess/issues/119>`_)
- Ignore zombie processes which are erroneously considered alive with python 3.11
(`#117 <https://github.com/pytest-dev/pytest-xprocess/issues/117>`_)

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ classifiers=
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Topic :: Software Development :: Testing
Topic :: Software Development :: Libraries
Topic :: Utilities
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist=begin,py{37,38,39}
envlist=begin,py{37,38,39,310}

[testenv:dev]
commands =
Expand Down
27 changes: 16 additions & 11 deletions xprocess/xprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class XProcessResources:
def __init__(self, timeout):
self.timeout = timeout
# handle to the process logfile
self.fhandle = None
self.fhandles = []
# XProcessInfo holding information on XProcess instance
self.info = None
# Each XProcess will have a related Popen instance
Expand All @@ -149,13 +149,14 @@ def __del__(self):

def __repr__(self):
return "<XProcessResources {}, {}, {}>".format(
self.fhandle, self.info, self.popen
self.fhandles, self.info, self.popen
)

def release(self):
# file handles should always be closed
# in order to avoid ResourceWarnings
self.fhandle.close()
for fhandle in self.fhandles:
fhandle.close()

# We should wait on procs exit status if
# termination signal has been issued
Expand Down Expand Up @@ -268,28 +269,32 @@ def ensure(self, name, preparefunc, restart=False):
self.log.debug("process %r started pid=%s", name, pid)
stdout.close()

# keep track of all file handles so we can
# cleanup later during teardown phase
xresource.fhandle = info.logpath.open()
log_file_handle = info.logpath.open()

# skip previous process logs
lines = info.logpath.open().readlines()
process_log_block_handle = info.logpath.open()
lines = process_log_block_handle.readlines()
if lines:
proc_block_counter = sum(
1 for line in lines if XPROCESS_BLOCK_DELIMITER in line
)
for line in xresource.fhandle:
for line in log_file_handle:
if XPROCESS_BLOCK_DELIMITER in line:
proc_block_counter -= 1
if proc_block_counter <= 0:
break

# keep track of all file handles so we can
# cleanup later during teardown phase
xresource.fhandles.append(log_file_handle)
xresource.fhandles.append(process_log_block_handle)

self.resources.append(xresource)

if not restart:
xresource.fhandle.seek(0, 2)
log_file_handle.seek(0, 2)
else:
if not starter.wait(xresource.fhandle):
if not starter.wait(log_file_handle):
raise RuntimeError(
"Could not start process {}, the specified "
"log pattern was not found within {} lines.".format(
Expand All @@ -299,7 +304,7 @@ def ensure(self, name, preparefunc, restart=False):
self.log.debug("%s process startup detected", name)

pytest_extlogfiles = self.config.__dict__.setdefault("_extlogfiles", {})
pytest_extlogfiles[name] = xresource.fhandle
pytest_extlogfiles[name] = log_file_handle
self.getinfo(name)

return info.pid, info.logpath
Expand Down

0 comments on commit 167f269

Please sign in to comment.