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

[🐛 Bug]: Python - not shutting down Firefox/Geckodriver when service gets garbage collected #15399

Open
cgoldberg opened this issue Mar 10, 2025 · 6 comments
Labels
A-needs-triaging A Selenium member will evaluate this soon! C-py Python Bindings I-defect Something is not working as intended

Comments

@cgoldberg
Copy link
Contributor

What happened?

(this issue is kind of long, but this behavior has been driving me crazy and I needed to document it so hopefully we can fix it)

Expected behavior:

When a Python script completes, Firefox and Geckodriver processes are terminated.

Actual behavior:

When a Python script completes, Firefox and Geckodriver processes are still running.

Why should they not be running?

Normally, you would launch Firefox by calling driver = webdriver.Firefox(). Once launched, you can call driver.quit() to stop Firefox and Geckodriver. This works fine.

Alternately, you can launch Firefox by first creating a service = webdriver.firefox.service.Service() instance and using that when calling driver = webdriver.Firefox(service=service). After that, you can stop the service with service.stop(). This should stop the Geckodriver process from running. This works fine.

However, if you launch Firefox, then let your script end, the underlying service object gets destroyed during garbage collection and its __del__ method gets called. Inside service.__del__() is a call to service.stop(), which should stop the service and kill Geckodriver. For some reason, this isn't working and it leaves Firefox open and a geckodriver process running.

Firefox/Geckodriver behaves different than other browsers/drivers:

This is most noticeable by the difference in how Firefox behaves compared to other browsers.

For example, run the following code in a Python script (this won't be reproducible from an interactive interpreter, so make sure you create a script and call it from the command line):

from selenium import webdriver
driver = webdriver.Firefox()

After this script completes, you will notice Firefox is still visible and a geckodriver process is active:

$ ps | grep geckodriver
31328 pts/2    00:00:00 geckodriver
$

Now compare that to Chrome:

from selenium import webdriver
driver = webdriver.Chrome()

After this script completes, Chrome closes and and no chromedriver process is active:

$ ps | grep chromedriver
$

I think the Chrome behavior is correct and the Firefox is not.

This is most likely happening somewhere in py/selenium/webdriver/common/service.py ... but I've been through the code and can't figure out why Firefox isn't properly terminated. It would be great to figure this out and have consistent behavior across all browsers/drivers.

How can we reproduce the issue?

from selenium import webdriver
driver = webdriver.Firefox()

Relevant log output

DEBUG:selenium.webdriver.common.selenium_manager:Selenium Manager binary found at: /home/cgoldberg617/code/selenium/py/selenium/webdriver/common/linux/selenium-manager
DEBUG:selenium.webdriver.common.selenium_manager:Executing process: /home/cgoldberg617/code/selenium/py/selenium/webdriver/common/linux/selenium-manager --browser firefox --debug --language-binding python --output json
DEBUG:selenium.webdriver.common.selenium_manager:Sending stats to Plausible: Props { browser: "firefox", browser_version: "", os: "linux", arch: "x86_64", lang: "python", selenium_version: "4.29" }
DEBUG:selenium.webdriver.common.selenium_manager:geckodriver not found in PATH
DEBUG:selenium.webdriver.common.selenium_manager:firefox not found in PATH
DEBUG:selenium.webdriver.common.selenium_manager:firefox not found in the system
DEBUG:selenium.webdriver.common.selenium_manager:Required browser: firefox 136.0
DEBUG:selenium.webdriver.common.selenium_manager:firefox 136.0 already exists
DEBUG:selenium.webdriver.common.selenium_manager:firefox 136.0 is available at /home/cgoldberg617/.cache/selenium/firefox/linux64/136.0/firefox
DEBUG:selenium.webdriver.common.selenium_manager:Valid geckodriver versions for firefox 136: ["0.36.0", "0.35.0", "0.34.0"]
DEBUG:selenium.webdriver.common.selenium_manager:Required driver: geckodriver 0.36.0
DEBUG:selenium.webdriver.common.selenium_manager:geckodriver 0.36.0 already in the cache
DEBUG:selenium.webdriver.common.selenium_manager:Driver path: /home/cgoldberg617/.cache/selenium/geckodriver/linux64/0.36.0/geckodriver
DEBUG:selenium.webdriver.common.selenium_manager:Browser path: /home/cgoldberg617/.cache/selenium/firefox/linux64/136.0/firefox
DEBUG:selenium.webdriver.common.service:Started executable: `/home/cgoldberg617/.cache/selenium/geckodriver/linux64/0.36.0/geckodriver` in a child process with pid: 31845 using 0 to output -3
DEBUG:selenium.webdriver.remote.remote_connection:POST http://localhost:39093/session {'capabilities': {'firstMatch': [{}], 'alwaysMatch': {'browserName': 'firefox', 'acceptInsecureCerts': True, 'moz:debuggerAddress': True, 'pageLoadStrategy': <PageLoadStrategy.normal: 'normal'>, 'browserVersion': None, 'moz:firefoxOptions': {'binary': '/home/cgoldberg617/.cache/selenium/firefox/linux64/136.0/firefox', 'prefs': {'remote.active-protocols': 3}}}}}
DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): localhost:39093
DEBUG:urllib3.connectionpool:http://localhost:39093 "POST /session HTTP/1.1" 200 0
DEBUG:selenium.webdriver.remote.remote_connection:Remote response: status=200 | data={"value":{"sessionId":"69af9487-5782-433a-9cdb-ce8b764d5c4a","capabilities":{"acceptInsecureCerts":true,"browserName":"firefox","browserVersion":"136.0","moz:accessibilityChecks":false,"moz:buildID":"20250227124745","moz:debuggerAddress":"127.0.0.1:59767","moz:geckodriverVersion":"0.36.0","moz:headless":false,"moz:platformVersion":"6.6.65-06377-gaae6fc9ba7df","moz:processID":31848,"moz:profile":"/tmp/rust_mozprofileoDQJGh","moz:shutdownTimeout":60000,"moz:webdriverClick":true,"moz:windowless":false,"pageLoadStrategy":"normal","platformName":"linux","proxy":{},"setWindowRect":true,"strictFileInteractability":false,"timeouts":{"implicit":0,"pageLoad":300000,"script":30000},"unhandledPromptBehavior":"dismiss and notify","userAgent":"Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0"}}} | headers=HTTPHeaderDict({'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache', 'content-length': '814', 'date': 'Mon, 10 Mar 2025 22:10:51 GMT'})
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request

Operating System

Linux (Debian Stable)

Selenium version

Python Selenium 4.30

What are the browser(s) and version(s) where you see this issue?

Firefox 136.0 (64-bit)

What are the browser driver(s) and version(s) where you see this issue?

Geckodriver 0.36.0

Are you using Selenium Grid?

No

@cgoldberg cgoldberg added bug C-py Python Bindings I-defect Something is not working as intended A-needs-triaging A Selenium member will evaluate this soon! labels Mar 10, 2025
Copy link

@cgoldberg, thank you for creating this issue. We will troubleshoot it as soon as we can.


Info for maintainers

Triage this issue by using labels.

If information is missing, add a helpful comment and then I-issue-template label.

If the issue is a question, add the I-question label.

If the issue is valid but there is no time to troubleshoot it, consider adding the help wanted label.

If the issue requires changes or fixes from an external project (e.g., ChromeDriver, GeckoDriver, MSEdgeDriver, W3C), add the applicable G-* label, and it will provide the correct link and auto-close the issue.

After troubleshooting the issue, please add the R-awaiting answer label.

Thank you!

@shbenzer
Copy link
Contributor

Do you know if this is the case in other languages or just Python?

@cgoldberg
Copy link
Contributor Author

I only use Python, but I'll try it in Ruby or Java when I get a chance.

I suspect it's only Python and has something to do with the __del__() method on the service class that gets called when the object is destroyed. I've always disliked having that method at all (and it's generally advised against using it in Python). I'd prefer the browser/driver only gets closed and cleaned up when you call quit()... but I suppose it is convenient and helps with leaving zombie driver processes around.

@shbenzer
Copy link
Contributor

shbenzer commented Mar 21, 2025

I’ll try to remember to take a deeper look when I have a chance and am not on my phone, but you might be right about it coming from service.py

Good catch

EDIT: Verfied in Python, driver hangs in Firefox but not Chrome.

@shbenzer
Copy link
Contributor

shbenzer commented Mar 26, 2025

Here are the logs for both browsers:

Firefox:

1743008832137	geckodriver	INFO	Listening on [REDACTED]
1743008832153	mozrunner::runner	INFO	Running command: MOZ_CRASHREPORTER="1" MOZ_CRASHREPORTER_NO_REPORT="1" MOZ_CRASHREPORTER_SHUTDOWN="1" MOZ_NO_REMOTE="1" "/Use ... s" "localhost" "-foreground" "-no-remote" "-profile" "[REDACTED]"
console.warn: services.settings: Ignoring preference override of remote settings server
console.warn: services.settings: Allow by setting MOZ_REMOTE_SETTINGS_DEVTOOLS=1 in the environment
1743008832311	Marionette	INFO	Marionette enabled
console.error: "Warning: unrecognized command line flag" "-remote-allow-hosts"
1743008832544	Marionette	INFO	Listening on port [REDACTED]
WebDriver BiDi listening on ws://[REDACTED]
Read port: [REDACTED]
1743008832677	RemoteAgent	WARN	TLS certificate errors will be ignored for this session
UNSUPPORTED (log once): POSSIBLE ISSUE: unit 1 GLD_TEXTURE_INDEX_2D is unloadable and bound to sampler type (Float) - using zero texture because texture unloadable
2025-03-26 13:07:12.975 firefox[REDACTED] +[IMKClient subclass]: chose IMKClient_Modern
2025-03-26 13:07:12.975 firefox[REDACTED] +[IMKInputSession subclass]: chose IMKInputSession_Modern
DevTools listening on ws://127.0.0.1:[REDACTED]/devtools/browser/[REDACTED]

When I manually close the hanging browser, I get this:

1743009032283	Marionette	INFO	Stopped listening on port [REDACTED]

Chrome:

[1743008742.745][INFO]: Starting ChromeDriver 134.0.6998.165 (********) on port ******
[1743008742.745][INFO]: Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
[1743008742.760][INFO]: ChromeDriver was started successfully on port ******
[1743008742.808][INFO]: [********] COMMAND InitSession {
   "capabilities": {
      "alwaysMatch": {
         "browserName": "chrome",
         "browserVersion": null,
         "goog:chromeOptions": {
            "args": [  ],
            "binary": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
            "extensions": [  ]
         },
         "pageLoadStrategy": "normal"
      },
      "firstMatch": [ {
      } ]
   }
}
[1743008742.810][INFO]: Populating Preferences file: {
   "alternate_error_pages": { "enabled": false },
   "autofill": { "enabled": false },
   "browser": { "check_default_browser": false },
   "distribution": { "import_bookmarks": false, "import_history": false, "import_search_engine": false, "make_chrome_default_for_user": false, "skip_first_run_ui": true },
   "dns_prefetching": { "enabled": false },
   "profile": {
      "content_settings": { "pattern_pairs": { "https://*,*": { "media-stream": { "audio": "Default", "video": "Default" } } } },
      "default_content_setting_values": { "geolocation": 1 },
      "default_content_settings": { "geolocation": 1, "mouselock": 1, "notifications": 1, "popups": 1, "ppapi-broker": 1 },
      "password_manager_enabled": false
   },
   "safebrowsing": { "enabled": false },
   "search": { "suggest_enabled": false },
   "translate": { "enabled": false }
}
[1743008742.810][INFO]: Populating Local State file: {
   "background_mode": { "enabled": false },
   "ssl": { "rev_checking": { "enabled": false } }
}
[1743008742.810][INFO]: ChromeDriver supports communication with Chrome via pipes. This is more reliable and more secure.
[1743008742.810][INFO]: Use the --remote-debugging-pipe Chrome switch instead of the default --remote-debugging-port to enable this communication mode.
[1743008742.811][INFO]: Launching chrome: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome --allow-pre-commit-input --disable-background-networking --disable-client-side-phishing-detection --disable-default-apps --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-logging --log-level=0 --no-first-run --no-service-autorun --password-store=basic --remote-debugging-port=0 --test-type=webdriver --use-mock-keychain --user-data-dir=/var/folders/***/T/.org.chromium.Chromium.**** data:,
[1743008743.829][INFO]: resolved localhost to ["::1","127.0.0.1"]
[1743008743.853][INFO]: [********] RESPONSE InitSession {
   "capabilities": {
      "acceptInsecureCerts": false,
      "browserName": "chrome",
      "browserVersion": "134.0.6998.166",
      "chrome": {
         "chromedriverVersion": "134.0.6998.165 (********)",
         "userDataDir": "/var/folders/***/T/.org.chromium.Chromium.****"
      },
      "fedcm:accounts": true,
      "goog:chromeOptions": { "debuggerAddress": "localhost:*****" },
      "networkConnectionEnabled": false,
      "pageLoadStrategy": "normal",
      "platformName": "mac",
      "proxy": { },
      "setWindowRect": true,
      "strictFileInteractability": false,
      "timeouts": { "implicit": 0, "pageLoad": 300000, "script": 30000 },
      "unhandledPromptBehavior": "dismiss and notify",
      "webauthn:extension:credBlob": true,
      "webauthn:extension:largeBlob": true,
      "webauthn:extension:minPinLength": true,
      "webauthn:extension:prf": true,
      "webauthn:virtualAuthenticators": true
   },
   "sessionId": "********"
}
[1743008743.866][INFO]: [********] COMMAND QuitAll { }
[1743008743.916][INFO]: [********] RESPONSE QuitAll

Ideally we'd know when this started happening, but that isn't an easy answer to find. If it's just an issue in the python bindings, it shouldn't be a hard fix. Without knowing the cause, I can see how it could be Mozilla code, and I can see how it could be our code.

EDIT:

Here's code to check the logs:

from selenium import webdriver

if __name__ == "__main__":
    service = webdriver.firefox.service.Service(log_output="geckodriver.log")
    driver = webdriver.Firefox(service=service)

    service = webdriver.chrome.service.Service(log_output="chromedriver.log")
    driver = webdriver.Chrome(service=service)

@titusfortner
Copy link
Member

I think this may be related to
#11303

@titusfortner titusfortner removed the bug label Mar 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-needs-triaging A Selenium member will evaluate this soon! C-py Python Bindings I-defect Something is not working as intended
Projects
None yet
Development

No branches or pull requests

3 participants