Repository: pypa/virtualenv
Pull request #2617 (2023-08-04 09:13:52)

fix ResourceWarning on process exit (#2472)
### Thanks for contributing, make sure you address all the checklists (for details on how see [development documentation](https://virtualenv.pypa.io/en/latest/development.html#development))

- [x] ran the linter to address style issues (`tox -e fix`)
- [x] wrote descriptive pull request text - see #2472
- [] ensured there are test(s) validating the fix
- [x] added news fragment in `docs/changelog` folder
- [] updated/extended the documentation


sigma67: @gaborbernat I decided on the simpler solution with the context manager to let the stdlib do the heavy lifting

Patch:
diff --git a/docs/changelog/2472.bugfix.rst b/docs/changelog/2472.bugfix.rst
new file mode 100644
index 00000000..02effe48
--- /dev/null
+++ b/docs/changelog/2472.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed ResourceWarning on exit caused by periodic update subprocess
diff --git a/docs/installation.rst b/docs/installation.rst
index eebac4d3..ab582034 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -90,8 +90,9 @@ supported on a best effort approach.
 CPython is shipped in multiple forms, and each OS repackages it, often applying some customization along the way.
 Therefore we cannot say universally that we support all platforms, but rather specify some we test against. In case
 of ones not specified here the support is unknown, though likely will work. If you find some cases please open a feature
-request on our issue tracker. Note, as of ``20.18.0`` we no longer support running under Python less than 3.7, however
-we still support creating environments for 2.7 and 3.5.
+request on our issue tracker. Note, as of ``20.18.0`` we no longer support running under Python less than 3.7. Also, as
+of ``20.22.0`` we no longer support creating environments for Python 2.7, 3.5 and 3.6.
+
 
 Linux
 ~~~~~
diff --git a/src/virtualenv/seed/wheels/periodic_update.py b/src/virtualenv/seed/wheels/periodic_update.py
index 7d743540..3d0239d4 100644
--- a/src/virtualenv/seed/wheels/periodic_update.py
+++ b/src/virtualenv/seed/wheels/periodic_update.py
@@ -12,7 +12,7 @@ from datetime import datetime, timedelta, timezone
 from itertools import groupby
 from pathlib import Path
 from shutil import copy2
-from subprocess import PIPE, Popen
+from subprocess import DEVNULL, Popen
 from textwrap import dedent
 from threading import Thread
 from urllib.error import URLError
@@ -216,7 +216,7 @@ def trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, e
         .format(distribution, for_py_version, wheel_path, str(app_data), [str(p) for p in search_dirs], periodic),
     ]
     debug = env.get("_VIRTUALENV_PERIODIC_UPDATE_INLINE") == "1"
-    pipe = None if debug else PIPE
+    pipe = None if debug else DEVNULL
     kwargs = {"stdout": pipe, "stderr": pipe}
     if not debug and sys.platform == "win32":
         kwargs["creationflags"] = CREATE_NO_WINDOW
@@ -230,6 +230,9 @@ def trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, e
     )
     if debug:
         process.communicate()  # on purpose not called to make it a background process
+    else:
+        # set the returncode here -> no ResourceWarning on main process exit if the subprocess still runs
+        process.returncode = 0
 
 
 def do_update(distribution, for_py_version, embed_filename, app_data, search_dirs, periodic):  # noqa: PLR0913


Test patch:
diff --git a/tests/unit/seed/wheels/test_periodic_update.py b/tests/unit/seed/wheels/test_periodic_update.py
index 32931bfe..85756d93 100644
--- a/tests/unit/seed/wheels/test_periodic_update.py
+++ b/tests/unit/seed/wheels/test_periodic_update.py
@@ -316,7 +316,7 @@ def test_trigger_update_no_debug(for_py_version, session_app_data, tmp_path, moc
     )
 
     assert args == ([sys.executable, "-c", cmd],)
-    expected = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE}
+    expected = {"stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL}
     if sys.platform == "win32":
         expected["creationflags"] = CREATE_NO_WINDOW
     assert kwargs == expected