Skip to content

Experimental run-tests.php parallelisation #2822

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

Closed

Conversation

hikari-no-yume
Copy link
Contributor

@hikari-no-yume hikari-no-yume commented Oct 8, 2017

Moved to #3838.

====⚡️===========================================================⚡️====
====⚡️==== WELCOME TO THE FUTURE: run-tests PARALLEL EDITION ====⚡️====
====⚡️===========================================================⚡️====

You know it.

Only look at changes after the first commit if you want to understand the diff; the first commit is merely a minor refactoring, but because it moves code around it creates a lot of git diff noise. Alternatively, look at the whitespace-ignoring diff.

You should probably read my email to internals: http://news.php.net/php.internals/100838

@hikari-no-yume hikari-no-yume force-pushed the parallelised-run-tests branch from 3807427 to 0d1e4b5 Compare October 8, 2017 03:53
@hikari-no-yume
Copy link
Contributor Author

Argh, I notice now this has mixed tabs and spaces. Thanks PHPStorm.

run-tests.php Outdated
global $workerID;

@unlink(__DIR__ . "/../worker$workerID.log");
ini_set("error_log", __DIR__ . "/../worker$workerID.log");
Copy link
Contributor Author

@hikari-no-yume hikari-no-yume Oct 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to remove debug code here, perhaps because it never worked in the first place.

I'd like to add an error handler that sends a message with the error.

run-tests.php Outdated
@@ -2655,34 +2955,32 @@ function show_summary()
}

function show_redirect_start($tests, $tested, $tested_file)
{
global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS;
{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops.

run-tests.php Outdated

$workerID = 0;
if (getenv("TEST_PHP_WORKER")) {
$workerID = intval(getenv("TEST_PHP_WORKER"), 10);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just use $workerID = (int) getenv("TEST_PHP_WORKER");.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. Right, yeah. I think I was thinking of JS parseInt() when I wrote this.

run-tests.php Outdated
if (substr(PHP_OS, 0, 3) == "WIN") {
$pass_options .= " -c " . escapeshellarg($conf_passed);
} else {
$pass_options .= " -c '$conf_passed'";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no escapeshellarg here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't write this code, so I don't know.

run-tests.php Outdated
echo "Spawning workers… ";
for ($i = 1; $i <= $workers; $i++) {
$proc = proc_open(
$thisPHP . ' ' . escapeshellarg($thisScript),
Copy link
Member

@kelunik kelunik Oct 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably set some ini options, such as those, to prevent unexpected messages on STDOUT.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would make sense to log to stderr.

That said, wouldn't writing a user error handler that sends errors as messages work? This is my current plan.

@hikari-no-yume hikari-no-yume force-pushed the parallelised-run-tests branch from 0d1e4b5 to 91d19cb Compare October 8, 2017 13:00
@hikari-no-yume
Copy link
Contributor Author

Did some rebase-fu to fix the accidental use of spaces rather than tabs. At least, all the usages that were my fault ;)

@kelunik
Copy link
Member

kelunik commented Oct 8, 2017

Could you try enabling it on Travis and see whether it can speed up the tests there, too?

@hikari-no-yume
Copy link
Contributor Author

@kelunik Very sensible idea. I'll add it maybe as an extra task or whatever so I can see what's broken by the parallelism.

@hikari-no-yume hikari-no-yume force-pushed the parallelised-run-tests branch 2 times, most recently from b8be2ec to d713b17 Compare October 8, 2017 21:33
@hikari-no-yume
Copy link
Contributor Author

hikari-no-yume commented Oct 8, 2017

@kelunik I'm happy to report that, judging by build #27751, it does indeed make Travis faster, at 530s to run tests with 2 worker children versus 908s in normal mode. Double the number of worker children to four and they can do it in 420s, which seems like a case of diminishing returns (I think Travis maybe only provides 2 logical CPUs, if that).

There's some weird issues in Travis, though, which tripped up my code's sanity checks. Apparently the workers are sending back more test results than the test files they were given to execute. I'm not sure why that is, maybe there's some run-tests.php feature I failed to account for, or maybe somehow messages are getting repeated (how would that happen, though?). The test summary totals look the same (aside from extra failures) as non-parallel builds' though, so probably it's the former and I shouldn't be worried. Nikita says it might be REDIR tests so I'll look into that. (Spoiler alert: I didn't know what REDIR tests do. If you run a REDIR test, it goes and runs more tests. Thus, the “total” number of tests to run is actually only a subset once you count the tests run by REDIR tests.)

@hikari-no-yume hikari-no-yume force-pushed the parallelised-run-tests branch from d713b17 to 9a825e3 Compare October 8, 2017 22:14
run-tests.php Outdated
$toRead = array_values($workerStdouts);
$toWrite = NULL;
$toExcept = NULL;
if (stream_select($toRead, $toWrite, $toExcept, 0, 50 * 1000)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kelunik I'd guess you know what you're doing here better than I do, so: Would you see any disadvantage to having a higher timeout here, say 1 second? Checking the reads on these is supposedly non-blocking, and there's nothing else the host process needs to do except for wait on reads, so reducing the number of syscalls very slightly would make sense, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can totally increase that to 10 seconds or even more. It will be interrupted by any signals or actionable streams.

@hikari-no-yume hikari-no-yume force-pushed the parallelised-run-tests branch from ef1fbb0 to 1b71858 Compare October 8, 2017 23:57
@hikari-no-yume
Copy link
Contributor Author

hikari-no-yume commented Oct 9, 2017

There's now a flag file (you just touch it, in traditional UNIX fashion) you can put in a test directory, !CAN_BE_PARALLELISED, which tells run-tests.php that there's nothing wrong with executing different files in that directory concurrently, and it will divide that directory into chunks which can be dispatched to different workers.

I've added that file to a bunch of large directories that seemed safe, and it hasn't broken anything. Unfortunately, it also hasn't improved effective test execution time for me, even on my 16-thread machine; no matter how fast you execute everything else, ext/standard/tests/file/ will drag things out. So the next step will be to break that directory up a bit by hand, if possible, and have a look at the other directories I haven't flagged for now.

$workerStdouts = [];
$workerStderrs = [];

echo "====⚡️===========================================================⚡️====\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the emoji here is a good idea. it will cause problems in Windows/CMD.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't recommend proc_open on Windows anyway, because STDIO are file handles there and block.

Copy link
Contributor Author

@hikari-no-yume hikari-no-yume Oct 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick testing on Windows 10 shows that in cmd, the emoji don't cause problems, they just show up as empty rectangles. That's fine, really. Maybe it can serve as an incentive to support Unicode terminal output.

And @kelunik is right about proc_open blocking… it seemingly ignores all all the workers but one because of it. :/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty rectangles IS a problem, isn't it? If you insist on these emoji, I suggest you add something like --no-ansi, and turn it on on windows.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just cosmetic, so why bother? And actually, it's just a font support problem. Copy and paste them into another window and it shows up fine. I guess PHP does use Unicode after all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I began modifying the code so it can also communicate over sockets, but this is a little more painful than I expected. If I get that working, it should solve the Windows problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…oh, I was using the wrong socket API >.>

@staabm
Copy link
Contributor

staabm commented Oct 10, 2017

@hikari-no-yume IIRC @sgolemon has a list of all tests which can ran in parallel (which was created for hhvm in the past)

@hikari-no-yume
Copy link
Contributor Author

hikari-no-yume commented Oct 11, 2017

Since, as @kelunik pointed out, Windows's proc_open pipes don't support non-blocking I/O, I added a slightly different code-path that uses IPv6 loopback TCP sockets instead in that case. Now parallelised run-tests works on Windows – and quite well :)

run-tests.php -j16 on my Ryzen 7 1700 in Windows PowerShell

@KalleZ
Copy link
Member

KalleZ commented Oct 17, 2017

@hikari-no-yume I suggest we perhaps do a cleanup of tests in Zend/ and put them in sub directories and then merge this but that can even be done after, so lets have a go at this in master.

Personally the only negative I have about this, as I stated on Twitter, is the annoying square boxes on my CLI

@jhdxr
Copy link
Member

jhdxr commented Oct 17, 2017

Again, I suggest to remove, or at least add an option for the emoji. I fully agree they are very cute and I like these cute things. But they are not cute any more when they become squares. The worse thing is for people without related knowledge, they may treat this as a problem of their cmd / php tests, because the abnormal character usually means something wrong.

@jhdxr
Copy link
Member

jhdxr commented Oct 17, 2017

Anyway, the parallel testing itself is awosome. Let RM make the decision.

run-tests.php Outdated
$workerInput = $workerSock;
$workerOutput = $workerSock;
} else {
$workerInput = $pipes[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to use sockets on all platforms to reduce code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. Not using sockets means a simpler codepath less likely to face problems, though.

run-tests.php Outdated

if ($useSockets) {
// IPv6 because nobody uses it, so less chance of collisions, right? ;)
$port = (ord('<') << 8) + ord('?');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a magic constant port. use stream_socket_server("tcp://[::1]:0"), and an unused port will be selected.

$a=stream_socket_server("tcp://[::1]:0");
var_dump(stream_socket_get_name($a, false));
//php shell code:1:
//string(9) "::1:52491"

and then we can use ipv4. or at least use it as a fallback.

Copy link
Contributor Author

@hikari-no-yume hikari-no-yume Oct 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I thought about using :0, I'll try implementing it.

I don't see any value in adding IPv4. Who needs to test PHP 7.3 in parallel on a system with no support for IPv6? It's not like IPv6 connectivity is required, just loopback.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hikari-no-yume There's 0 reason to use IPv6. Binding to IPv4 will usually bind both anyway, see http://php.net/manual/en/context.socket.php + ipv6_v6only.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know there is almost no IPv6 in China, so sometimes the system administrator will disable all the IPv6 components because they believe it can speed up the system. It's silly but it happens.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hikari-no-yume side note, IPv6 support is ALWAYS available on Windows, but can be disabled in custom builds for some odd reason I do not understand

@hikari-no-yume
Copy link
Contributor Author

@KalleZ Zend's fine, it doesn't need breaking up. I mean, it would be nice to, but this thing already subdivides it automatically with few (no? I don't remember) problems.

@KalleZ
Copy link
Member

KalleZ commented Oct 17, 2017

@hikari-no-yume lets give it a try in master, then we can all hack on it together, there is nothing else holding it back

@jhdxr though we keep run-tests.php in sync across all branches, it should be perfectly fine to play around with in master, I'm sure the RMs for lower branches would merge it once it had some time there

@hikari-no-yume
Copy link
Contributor Author

@KalleZ we do keep it in sync? Oh, okay. There's changes that aren't synced right now, and this code will only work on 7.2 as-is…

@hikari-no-yume hikari-no-yume force-pushed the parallelised-run-tests branch from 871febd to 69b648a Compare July 5, 2018 21:21
@hikari-no-yume
Copy link
Contributor Author

Rebased for current master.

@hikari-no-yume
Copy link
Contributor Author

The CI builds fail because it seems to choke on very large test output… fgets is only fetching 8192 bytes. A suspicious number. I'll raise the line length limit and see if that fixes it.

run-tests.php Outdated
error("Could not find worker stdout in array of worker stdouts, THIS SHOULD NOT HAPPEN.");
}
// Some tests have *very* long output and fgets() seems to truncate to 8KiB unless we say we want more
while (FALSE !== ($rawMessage = fgets($workerSock, 64 * 1024))) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work… can fgets not handle non-blocking streams or something? :/

while (FALSE !== ($rawMessage = fgets($workerSock, 64 * 1024))) {
$rawMessageBuffer = '';
while (FALSE !== ($rawMessage = fgets($workerSock))) {
// work around fgets truncating things
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also apparently doesn't work. I bet there's a non-blocking I/O error in here somewhere.

@hikari-no-yume hikari-no-yume force-pushed the parallelised-run-tests branch from 48d2dc6 to a15c666 Compare July 5, 2018 23:13
@hikari-no-yume
Copy link
Contributor Author

Woo, it doesn't choke on huge test output now.

@hikari-no-yume
Copy link
Contributor Author

And perhaps more importantly, very few tests fail with parallelised tests enabled under Travis. However I/O capture tests are skipped, so we couldn't safely use only parallel testing right now…

@nikic nikic changed the base branch from master to PHP-7.4 February 12, 2019 11:34
@nikic
Copy link
Member

nikic commented Feb 12, 2019

From what I remember, this PR implements parallelization on the directory level and adds marker files to exclude directories from parallelization.

I wanted to propose an alternative approach for handling tests that can't be run together, which I think should be both relatively simple to implement and allows a smooth transition from coarse-grained (to start with) to fine-grained (once we have time to review individual tests) handling:

Add a new --CONFLICTS-- section to the phpt format, which contains a list of strings (or possibly just one string to start with). All tests which have a non-empty intersection in CONFLICTS cannot be run together (but may still be run together with other tests). For example, ext/mysqli and ext/pdo_mysql would use --CONFLICTS-- \n mysql, while ext/pgsql and ext/pdo_pgsql would use --CONFLICTS -- \n pgsql. This prevents MySQL tests from running in parallel, but they can still run in parallel with PgSQL tests.

Additionally a CONFLICTS file can be added to a whole directory, so this doesn't need to be specified per-file.

@carusogabriel
Copy link
Contributor

@nikic Might be something, so we can delivery this :)

php-pulls pushed a commit that referenced this pull request Feb 16, 2019
Prerequisite for parallelised testing:
#2822

Quoth Nikita @
#3789 (comment):

> @hikari-no-yume Please feel free to directly commit the first commit
> (to PHP-7.4). That's probably a big and unnecessary source of
> conflicts, and makes reviewing harder as well.
@hikari-no-yume
Copy link
Contributor Author

As implied by 41fbeb6 I'm working on this again (thanks @KalleZ and @nikic for the reminder ^^). Rebasing it is gnarly though and I don't want to trash history for this branch again; I'm going to hand-rebase this on a new branch and then open a new PR once I'm done and close this one. Leaving this one open for the moment.

@hikari-no-yume
Copy link
Contributor Author

Superseding with #3838.

TysonAndre added a commit to TysonAndre/php-src that referenced this pull request Mar 23, 2019
Motivation:
As an extension author, I want to speed up running tests in php <=7.3,
both locally and in CI (e.g. with valgrind).
This can be done by manually copying php 7.4's run-tests.php script
to replace the one generated by `phpize`

- list() doesn't work in php 7.0
- negative string offset doesn't work in php 7.2

If run-tests.php can be copied from php-src without any manual patches,
that would be the easiest.

Related to php#2822 - I didn't see any discussion for/against compatibility
with older php versions
php-pulls pushed a commit that referenced this pull request Mar 25, 2019
Motivation:
As an extension author, I want to speed up running tests in php <=7.3,
both locally and in CI (e.g. with valgrind).
This can be done by manually copying php 7.4's run-tests.php script
to replace the one generated by `phpize`

- list() doesn't work in php 7.0
- negative string offset doesn't work in php 7.2

If run-tests.php can be copied from php-src without any manual patches,
that would be the easiest.

Related to #2822 - I didn't see any discussion for/against compatibility
with older php versions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants