You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR refactors the driver service lifecycle stop/cleanup to manage subprocess resources (stdin/sdtdout/stderr), so they are cleaned up even if the child subprocess has already exited.
Previously, stop() only performed subprocess cleanup when the child was still running. If the process somehow exited or was terminated externally before stop() was called, the parent-side pipe handles were never closed.
This change makes stop() always invoke the subprocess cleanup routine. The cleanup routine now only attempts to terminate the process if it is still running, but always closes the parent-side streams afterward. This prevents resource leaks while preserving the existing shutdown behavior for running processes.
🤖 AI assistance
No substantial AI assistance used
AI assisted (complete below)
Tool(s):
What was generated:
I reviewed all AI output and can explain the change
Ensure driver service stop() always closes subprocess streams
🐞 Bug fix🕐 10-20 Minutes
AI Description
• Always run subprocess cleanup in Service.stop(), even if the child already exited
• Only attempt termination when the subprocess is still running, preserving current shutdown
behavior
• Close stdin/stdout/stderr reliably to prevent parent-side file descriptor leaks
Diagram
graph TD
SVC["Service class"] --> STOP["stop()"] --> TERM["_terminate_process()"] --> CHILD(["Child subprocess"])
STOP --> LOG[("log_output fd")]
TERM --> STREAMS[("stdin/stdout/stderr")]
subgraph Legend
direction LR
_fn["Method"] ~~~ _proc(["Subprocess"]) ~~~ _res[("FDs/Streams")]
end
Loading
High-Level Assessment
The following are alternative approaches to this PR:
1. Use context-managed stream ownership (ExitStack)
➕ Centralizes stream acquisition/cleanup, reducing the chance of future leaks
➕ Makes lifecycle responsibilities explicit and testable
➖ More invasive refactor across service startup/pipe wiring
➖ Harder to adopt without broader API changes
2. Rely on Popen(close_fds=True) / avoid PIPEs where possible
➕ Reduces likelihood of FD leaks by limiting inherited handles
➕ May simplify resource management in some environments
➖ Does not eliminate the need to close pipes explicitly when PIPEs are used
➖ Potential behavior changes for consumers that depend on captured output
Recommendation: The PR’s approach is the right minimal fix: always invoke the cleanup routine from stop(), and gate only the termination logic on process liveness while unconditionally closing stdin/stdout/stderr. Alternatives add complexity or don’t fully address parent-side pipe closure.
Files changed (1) +20 / -20
Bug fix (1) +20 / -20
service.pyAlways cleanup subprocess streams even when service already exited+20/-20
Always cleanup subprocess streams even when service already exited
• Adjusts Service.stop() to always run subprocess cleanup when a process exists, rather than only when it is still running. Updates _terminate_process() to terminate/wait/kill only if the child is alive, but always closes stdin/stdout/stderr to prevent FD leaks.
1. Waits before closing pipes✗ Dismissed🐞 Bug☼ Reliability
Description
Service._terminate_process() now calls process.wait(60) before closing stdout/stderr, which
can delay shutdown (up to the full timeout) when the service was started with log_output=PIPE and
the child does not promptly exit on SIGTERM while writing to those pipes. Previously, closing the
streams occurred before waiting, reducing the chance that undrained pipes contribute to a prolonged
teardown.
+ if self.process.poll() is None:+ self.process.terminate()+ try:+ self.process.wait(60)+ except subprocess.TimeoutExpired:
Evidence
The codebase allows stdout/stderr to be configured as PIPE via log_output, but does not
provide any consumer that drains these pipes. With this PR, _terminate_process() blocks in
wait(60) before closing those streams, which can prolong teardown in the piped-output
configuration when the child does not promptly terminate on SIGTERM.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
### Issue description
`_terminate_process()` now waits for the child to exit *before* closing `stdin/stdout/stderr`. If `stdout/stderr` are set to `subprocess.PIPE` (via `log_output=PIPE`), Selenium does not drain these streams anywhere, and a child that delays/ignores SIGTERM while still writing can prolong shutdown until the 60s timeout.
### Issue Context
- `Service._start_process()` wires `stdout` and `stderr` to `self.log_output`, which may be `PIPE`.
- `Service.stop()` always calls `_terminate_process()` now.
### Fix Focus Areas
- py/selenium/webdriver/common/service.py[173-200]
- py/selenium/webdriver/common/service.py[214-240]
### Suggested fix approach
One of:
1) Close/drain streams before `wait()`:
- After `terminate()`, immediately close `stdin/stdout/stderr` (or at least `stdout/stderr` when they are pipes) before calling `wait(60)`.
2) Use `communicate()` to drain pipes safely:
- After `terminate()`, call `self.process.communicate(timeout=60)` (ignore returned output), then close streams in a `finally`.
- Keep the existing timeout/kill escalation logic.
Ensure the pipe-closing/draining happens before blocking on the process exiting when `stdout/stderr` are piped.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
2. stop() cleanup lacks test 📘 Rule violation▣ Testability
Description
The PR changes Service.stop()/_terminate_process() to always close subprocess streams even when
the child has already exited, but there is no regression test exercising this new cleanup behavior.
Without a test, future changes could reintroduce the file-descriptor leak or error-on-stop scenario
unnoticed.
PR changes now always call _terminate_process() from stop() and _terminate_process() closes
stdin/stdout/stderr regardless of whether the subprocess is still running, which is a
behavioral bug fix that should be covered by a regression test. The only existing unit test in
service_tests.py exercises start() failure and does not assert any stop() cleanup behavior.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
A bug fix changed `Service.stop()` / `_terminate_process()` to always perform subprocess stream cleanup, but there is no regression test that verifies streams are closed when the subprocess has already exited.
## Issue Context
The change is intended to prevent FD/handle leaks when the child process exits before `stop()` is called. The existing unit test for `Service` does not assert any `stop()` cleanup behavior.
## Fix Focus Areas
- py/selenium/webdriver/common/service.py[156-200]
- py/test/unit/selenium/webdriver/common/service_tests.py[27-45]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔗 Related Issues
Fixes #17887
💥 What does this PR do?
This PR refactors the driver service lifecycle stop/cleanup to manage subprocess resources (stdin/sdtdout/stderr), so they are cleaned up even if the child subprocess has already exited.
Previously,
stop()only performed subprocess cleanup when the child was still running. If the process somehow exited or was terminated externally beforestop()was called, the parent-side pipe handles were never closed.This change makes
stop()always invoke the subprocess cleanup routine. The cleanup routine now only attempts to terminate the process if it is still running, but always closes the parent-side streams afterward. This prevents resource leaks while preserving the existing shutdown behavior for running processes.🤖 AI assistance
💡 Additional Considerations
🔄 Types of changes