Skip to content

Commit 4417b4b

Browse files
committed
proc_open: Use posix_spawn(3) interface on systems where it is profitable
As the size of the PHP process increases, forking gets slower and memory consumption increases, degrading the performance in varying degrees. This patch makes proc_open use posix_spawn only on systems which is known to be safe, faster than the HAVE_FORK path and have posix_spawn_file_actions_addchdir_np(3) action. Non scientific benchmark shows running php own's test suite on linux completes dozens of seconds faster, the impact is probably higher on systems where posix_spawn is a syscall.
1 parent dfec0e4 commit 4417b4b

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

ext/standard/config.m4

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,8 @@ dnl
376376

377377
PHP_CHECK_FUNC(res_search, resolv, bind, socket)
378378

379+
PHP_CHECK_FUNC(posix_spawn_file_actions_addchdir_np)
380+
379381
dnl
380382
dnl Check for strptime()
381383
dnl

ext/standard/proc_open.c

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@
3636
#include <fcntl.h>
3737
#endif
3838

39+
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP
40+
/* Only defined on glibc >= 2.29, FreeBSD CURRENT, musl >= 1.1.24,
41+
* MacOS Catalina or later..
42+
* It should be posible to modify this so it is also
43+
* used in older systems when $cwd == NULL but care must be taken
44+
* as at least glibc < 2.24 has a legacy implementation known
45+
* to be really buggy.
46+
*/
47+
#include <spawn.h>
48+
#define USE_POSIX_SPAWN
49+
#endif
50+
3951
/* This symbol is defined in ext/standard/config.m4.
4052
* Essentially, it is set if you HAVE_FORK || PHP_WIN32
4153
* Other platforms may modify that configure check and add suitable #ifdefs
@@ -954,6 +966,36 @@ static zend_result set_proc_descriptor_from_resource(zval *resource, descriptors
954966
}
955967

956968
#ifndef PHP_WIN32
969+
#if defined(USE_POSIX_SPAWN)
970+
static zend_result close_parentends_of_pipes(posix_spawn_file_actions_t * actions, descriptorspec_item *descriptors, int ndesc)
971+
{
972+
int r;
973+
for (int i = 0; i < ndesc; i++) {
974+
if (descriptors[i].type != DESCRIPTOR_TYPE_STD) {
975+
r = posix_spawn_file_actions_addclose(actions, descriptors[i].parentend);
976+
if (r != 0) {
977+
php_error_docref(NULL, E_WARNING, "Cannot close file descriptor %d: %s", descriptors[i].parentend, strerror(r));
978+
return FAILURE;
979+
}
980+
}
981+
if (descriptors[i].childend != descriptors[i].index) {
982+
r = posix_spawn_file_actions_adddup2(actions, descriptors[i].childend, descriptors[i].index);
983+
if (r != 0) {
984+
php_error_docref(NULL, E_WARNING, "Unable to copy file descriptor %d (for pipe) into "
985+
"file descriptor %d: %s", descriptors[i].childend, descriptors[i].index, strerror(r));
986+
return FAILURE;
987+
}
988+
r = posix_spawn_file_actions_addclose(actions, descriptors[i].childend);
989+
if (r != 0) {
990+
php_error_docref(NULL, E_WARNING, "Cannot close file descriptor %d: %s", descriptors[i].childend, strerror(r));
991+
return FAILURE;
992+
}
993+
}
994+
}
995+
996+
return SUCCESS;
997+
}
998+
#else
957999
static zend_result close_parentends_of_pipes(descriptorspec_item *descriptors, int ndesc)
9581000
{
9591001
/* We are running in child process
@@ -977,6 +1019,7 @@ static zend_result close_parentends_of_pipes(descriptorspec_item *descriptors, i
9771019
return SUCCESS;
9781020
}
9791021
#endif
1022+
#endif
9801023

9811024
static void close_all_descriptors(descriptorspec_item *descriptors, int ndesc)
9821025
{
@@ -1188,6 +1231,35 @@ PHP_FUNCTION(proc_open)
11881231
childHandle = pi.hProcess;
11891232
child = pi.dwProcessId;
11901233
CloseHandle(pi.hThread);
1234+
#elif defined(USE_POSIX_SPAWN)
1235+
posix_spawn_file_actions_t factions;
1236+
int r;
1237+
posix_spawn_file_actions_init(&factions);
1238+
1239+
if (close_parentends_of_pipes(&factions, descriptors, ndesc) == FAILURE) {
1240+
posix_spawn_file_actions_destroy(&factions);
1241+
close_all_descriptors(descriptors, ndesc);
1242+
goto exit_fail;
1243+
}
1244+
1245+
if (cwd) {
1246+
/* This can fail only at spawn time */
1247+
php_ignore_value(posix_spawn_file_actions_addchdir_np(&factions, cwd));
1248+
}
1249+
1250+
if (argv) {
1251+
r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ));
1252+
} else {
1253+
r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
1254+
(char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
1255+
env.envarray ? env.envarray : environ);
1256+
}
1257+
posix_spawn_file_actions_destroy(&factions);
1258+
if (r != 0) {
1259+
close_all_descriptors(descriptors, ndesc);
1260+
php_error_docref(NULL, E_WARNING, "posix_spawn() failed: %s", strerror(r));
1261+
goto exit_fail;
1262+
}
11911263
#elif HAVE_FORK
11921264
/* the Unix way */
11931265
child = fork();

0 commit comments

Comments
 (0)