Skip to content

[🚀 Feature]: Python: better cleanup on Service.stop() #15393

@calestyo

Description

@calestyo

Feature and motivation

Hey.

I'm using selenium with WebKitWebDriver (which feels a bit more lightweight, and is packaged for Debian, unlike e.g. geckodriver) for some automation tasks.

Not sure if this is a bug, but it doesn't just work to do e.g.:

driver = webdriver.WebKitGTK()

(which errors out with Message: Unable to obtain driver for MiniBrowser; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors/driver_location?) but I really have to do e.g.

service = webdriver.WebKitGTKService(executable_path="/usr/bin/WebKitWebDriver", port=8080)
driver = webdriver.WebKitGTK(service=service)

which apparently starts WebKitWebDriver with -p 8080.

What I wanted to make sure is, that if my program ends/dies for whatever reason (including signals, well except SIGKILL[0]) then any of these subprocesses it starts are also killed.

I did something like:

import atexit
import signal

def cleanup():
    # the cleanup

def handler(signalnum, frame):
    sys.exit(128 + signalnum)

for s in (signal.SIGHUP, signal.SIGINT, signal.SIGQUIT, signal.SIGABRT, signal.SIGTERM):
    signal.signal(s, handler)
atexit.register(cleanup)

(the signal handler is needed because atexit would otherwise only work forSIGINT).

With WebKitWebDriver I get a whole bunch of child processes:

    PID    PPID    PGID   TPGID    SESS COMMAND
 283438  228886  283438  283438  228886 /usr/bin/python3 ./x.py
 283439  283438  283439  283438  228886   /usr/bin/WebKitWebDriver --port=8080
 283442  283439  283439  283438  228886     /usr/lib/x86_64-linux-gnu/webkit2gtk
 283466  283442  283439  283438  228886       /usr/lib/x86_64-linux-gnu/webkit2g
 283478  283442  283439  283438  228886       /usr/lib/x86_64-linux-gnu/webkit2g
 283441  283438  283438  283438  228886   [true] <defunct>

Now my natural candidate for # the cleanup would have been to call service.stop(), but that only kills the parent process (PID 283439 above), the various webkit2gtk... remain forever. Ugly.

I've seen Service.send_remote_shutdown_command() but TBH, I don't quite understand what it should do or whether one should use it.

It does at least not stop and processes (and especially not the children).

The main requests of this issue would be:

  1. Auto-handle that more properly. I.e. if you start a process, do so in a new process group and kill that (may not work on Windows?).. or something like that?

    Perhaps along the way, export the PGID for the user?
  2. And/or allow to use an executable_path= of e.g. None and don't start anything, but leave that task to the user (port=) would still be needed of course.

That's what I did now as a workaround, i.e. something like:

process = subprocess.Popen( ("/usr/bin/WebKitWebDriver", "--port=8080"), stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, process_group=0)

which I do before starting with selenium… and my cleanup handler is something like:

            process_group_id = process.pid
            
            os.killpg(process_group_id, signal.SIGTERM)
            process.terminate()
            
            time.sleep(0.5)
            
            os.killpg(process_group_id, signal.SIGKILL)
            process.kill()
            
            process.wait()

(with the process.terminate() and process.kill() being superfluous).

Selenium still tries to start it on it's own…

doesn't even notice that this fails, because the port is already bound to by my manually started driver, which I think should be a bug on it's own and then uses mine.

I can do:

service = webdriver.WebKitGTKService(executable_path="/bin/true", port=8080)

But that's still ugly (this is btw. the zombie true process in he listing above.

Thanks,
Chris.

[0] Might be possible somehow at least on Linux with prctl() and PR_SET_PDEATHSIG, but haven't found out how to use that on a process group (guess it's not directly possible).

Usage example

see above

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-pyPython BindingsI-enhancementSomething could be better

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions