pgrep mode: fail fast on bad args, and stop probing processes that are not PHP - #159
Open
rlerdorf wants to merge 1 commit into
Open
pgrep mode: fail fast on bad args, and stop probing processes that are not PHP#159rlerdorf wants to merge 1 commit into
rlerdorf wants to merge 1 commit into
Conversation
…e not PHP
Three ways `-P` wastes effort or hides a mistake.
A pattern containing a space silently breaks, forever. The argument string is
interpolated into a shell command, so `-P '-f phan/phan -f files'` becomes two
pgrep patterns; pgrep then fails on every poll with "only one pattern can be
provided" while phpspy keeps running and produces an empty output file. The
exit status was never checked -- neither pclose's nor popen's, which had no
else branch at all. Status >= 2 is now fatal and says what to look at ("no
match" is status 1 and stays the normal idle case).
The pattern usually matches phpspy itself. The text appears in phpspy's own
command line, and in the shell or sudo that launched it, so phpspy attaches to
those and spends four popen'd shell commands per poll discovering that bash is
not a PHP process. phpspy's own pid and its ancestors are now excluded --
walking PPid from status(5), not field 4 of stat(5), whose comm field can
contain spaces and parens.
Non-PHP matches are re-probed on every poll forever. A readlink of
/proc/<pid>/exe, falling back to matching `-w` against the mappings for
mod_php-style targets, gates the expensive path in two syscalls. It fails open
at every ambiguity: an unreadable /proc means "try it", never "skip it", so a
real target is never silently dropped.
And pids that cannot be attached to are now remembered for 30s rather than
re-queued on every poll. On a shared host with other users' PHP processes --
where /proc is unreadable, so the filter above correctly fails open and
find_addresses then fails -- that was 84 failed attach attempts per second
against 11 processes. Two strikes before sidelining a pid, because address
resolution legitimately fails for a process caught between fork and exec.
Together these take the same 3s run from ~250 failed attaches to 13.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Three ways
-Pwastes effort or hides a mistake. All found while profiling on a shared host.1. A pattern containing a space breaks silently, forever
The argument string is interpolated into a shell command, so the pattern's second word becomes a second pgrep pattern. phpspy keeps running and writes an empty file. The exit status was never checked — not
pclose's, andpopenhad noelsebranch at all, so a failure there just spun the loop quietly.Status >= 2 is now fatal and says what to look at. Status 1 ("no match") stays the normal idle case, which
test_pgrep_time_limit.shdepends on.2. The pattern usually matches phpspy itself
The pattern text appears in phpspy's own command line, and in the shell or sudo that launched it:
phpspy's own pid and its ancestors are now excluded. The ancestry walk reads
PPid:fromstatus(5)rather than field 4 ofstat(5), whosecommfield can contain spaces and parens.3. Non-PHP matches are re-probed on every poll
A
readlinkof/proc/<pid>/exe, falling back to matching-wagainst the mappings for mod_php-style targets, now gates the expensive path in two syscalls instead of fourpopen'd shell commands.This filter fails open at every ambiguity — an unreadable
/procmeans "try it", never "skip it" — so a real target is never silently dropped. That is deliberate, and it is also why the next part is needed.4. …which is that unattachable pids come back every poll
Because the filter correctly fails open on another user's PHP process (unreadable
/proc),find_addressesthen fails, and the producer re-queues the same pid on the next poll. On a shared host that was 84 failed attach attempts per second against 11 such processes.Pids that could not be attached to are now remembered for 30s. Two strikes before sidelining one, because address resolution legitimately fails for a process caught between
forkandexecand a single miss should not cost it half a minute.Together these take the same 3s run from roughly 250 failed attaches to 13.
Testing
make testpasses (16/16) under bothmakeandUSE_ZEND=1 make. Two new tests: a multi-word-Pmust fail promptly with a non-zero status rather than hang, and-P '-f phpspy'must produce no objdump probing at all.Independent of #156, #157 and #158.