fix: always wait on open command to avoid defunct processes
Without waiting on the xdg-open process on linux/freebsd, we end up with a defunct (zombie) process after each time we open a URL.
For example, after click on two URLs in a ghostty, here is the output of ps ux | grep xdg-open:
pbui 8364 0.0 0.0 0 0 tty7 Z+ 05:03 0:00 [xdg-open] <defunct>
pbui 8453 0.0 0.0 0 0 tty7 Z+ 05:03 0:00 [xdg-open] <defunct>
Perhaps we should revisit 695bc30, which removed the wait in the first place. On my machine running Alpine Linux 3.22, xdg-open does not stay alive and finishes immediately, thus making it safe to call wait (and not block). This is also the case on my other machine running Ubuntu 24.04: xdg-open launches the URL in a browser and terminates immediately.
Either way, this process must be waited upon eventually. Otherwise, we will accumulate a collection of defunct processes until the terminal itself terminates.
cc @gpanders who did this originally, the question I have is why does your xdg-open block? Mine also appears to not block, but I could be holding it wrong. Worst case we could spin up a thread that sits on wait.
I think a more complete solution would be #5988 which runs xdg-open in a background thread as some versions of xdg-open block waiting for completion instead of quickly exiting.
cc @gpanders who did this originally, the question I have is why does your
xdg-openblock? Mine also appears to not block, but I could be holding it wrong. Worst case we could spin up a thread that sits onwait.
I just tried this again and xdg-open no longer blocks for me either.
It's possible this was a quirk/bug with an older version of xdg-open? At the time I wrote that PR I was using Ubuntu 22.04, but am now using Ubuntu 24.04.
For what it's worth, a quick search shows a lot of people asking about xdg-open's blocking behavior:
https://stackoverflow.com/questions/77333067/why-does-xdg-open-block-on-command-line
https://bbs.archlinux.org/viewtopic.php?id=259672
It's probably safest to assume that xdg-open can block and reap the child process asynchronously.
It's probably safest to assume that xdg-open can block and reap the child process asynchronously.
Agreed. To keep things simple I'd just move the wait call to a thread, the spawn is already async no matter what and it simplifies memory lifetimes to an integer value (pid).
Ok, I took a stab at moving the wait to a detached thread. After some quick testing, it seems to wait properly and there are no defunct processes after clicking on a URL.
That said, this is my first time programming in Zig, so I'm not sure if I managed the lifetimes properly (struggled a bit with getting the types to the _openWait thread function correct).
I discovered that there was a race condition in a previous version where I was passing exe as a pointer to the thread function. Since this was on the stack, it was possible that it would get deallocated before the thread could run.
To fix this, I changed the code to pass exe by value and then fixed a compiler error by doing a @constCast on the struct when calling wait.
Thanks, this is looking good to me. I made some touchups, mostly style. I think functionally the most important is we must use log.warn and not log.err. log.err is for fatal messages.