diff --git a/ext/pcntl/config.m4 b/ext/pcntl/config.m4 index 7e7a389615a88..89475c44e6f85 100644 --- a/ext/pcntl/config.m4 +++ b/ext/pcntl/config.m4 @@ -7,7 +7,7 @@ if test "$PHP_PCNTL" != "no"; then AC_CHECK_FUNCS([fork], [], [AC_MSG_ERROR([pcntl: fork() not supported by this platform])]) AC_CHECK_FUNCS([waitpid], [], [AC_MSG_ERROR([pcntl: waitpid() not supported by this platform])]) AC_CHECK_FUNCS([sigaction], [], [AC_MSG_ERROR([pcntl: sigaction() not supported by this platform])]) - AC_CHECK_FUNCS([getpriority setpriority wait3 wait4 sigwaitinfo sigtimedwait unshare rfork forkx pidfd_open]) + AC_CHECK_FUNCS([getpriority setpriority wait3 wait4 sigwaitinfo sigtimedwait unshare rfork forkx pidfd_open sched_setaffinity]) AC_CHECK_TYPE([siginfo_t],[PCNTL_CFLAGS="-DHAVE_STRUCT_SIGINFO_T"],,[#include ]) diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index 4c4d01c27ce53..fcd2315e8c24f 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -42,8 +42,13 @@ #endif #include -#ifdef HAVE_UNSHARE +#if defined(HAVE_UNSHARE) || defined(HAVE_SCHED_SETAFFINITY) #include +#if defined(__FreeBSD__) +#include +#include +typedef cpuset_t cpu_set_t; +#endif #endif #ifdef HAVE_PIDFD_OPEN @@ -1476,6 +1481,123 @@ PHP_FUNCTION(pcntl_setns) } #endif +#ifdef HAVE_SCHED_SETAFFINITY +PHP_FUNCTION(pcntl_getcpuaffinity) +{ + zend_long pid; + bool pid_is_null = 1; + cpu_set_t mask; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(pid, pid_is_null) + ZEND_PARSE_PARAMETERS_END(); + + // 0 == getpid in this context, we're just saving a syscall + pid = pid_is_null ? 0 : pid; + + CPU_ZERO(&mask); + + if (sched_getaffinity(pid, sizeof(mask), &mask) != 0) { + PCNTL_G(last_error) = errno; + switch (errno) { + case ESRCH: + zend_argument_value_error(1, "invalid process (" ZEND_LONG_FMT ")", pid); + RETURN_THROWS(); + case EPERM: + php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges"); + break; + default: + php_error_docref(NULL, E_WARNING, "Error %d", errno); + } + + RETURN_FALSE; + } + + zend_ulong maxcpus = (zend_ulong)sysconf(_SC_NPROCESSORS_CONF); + array_init(return_value); + + for (zend_ulong i = 0; i < maxcpus; i ++) { + if (CPU_ISSET(i, &mask)) { + add_next_index_long(return_value, i); + } + } +} + +PHP_FUNCTION(pcntl_setcpuaffinity) +{ + zend_long pid; + bool pid_is_null = 1; + cpu_set_t mask; + zval *hmask = NULL, *ncpu; + + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(pid, pid_is_null) + Z_PARAM_ARRAY(hmask) + ZEND_PARSE_PARAMETERS_END(); + + if (!hmask || zend_hash_num_elements(Z_ARRVAL_P(hmask)) == 0) { + zend_argument_value_error(2, "must not be empty"); + RETURN_THROWS(); + } + + // 0 == getpid in this context, we're just saving a syscall + pid = pid_is_null ? 0 : pid; + zend_ulong maxcpus = (zend_ulong)sysconf(_SC_NPROCESSORS_CONF); + CPU_ZERO(&mask); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(hmask), ncpu) { + ZVAL_DEREF(ncpu); + zend_long cpu; + if (Z_TYPE_P(ncpu) != IS_LONG) { + if (Z_TYPE_P(ncpu) == IS_STRING) { + zend_ulong tmp; + if (!ZEND_HANDLE_NUMERIC(Z_STR_P(ncpu), tmp)) { + zend_argument_value_error(2, "cpu id invalid value (%s)", ZSTR_VAL(Z_STR_P(ncpu))); + RETURN_THROWS(); + } + + cpu = (zend_long)tmp; + } else { + zend_string *wcpu = zval_get_string_func(ncpu); + zend_argument_value_error(2, "cpu id invalid type (%s)", ZSTR_VAL(wcpu)); + zend_string_release(wcpu); + RETURN_THROWS(); + } + } else { + cpu = Z_LVAL_P(ncpu); + } + + if (cpu < 0 || cpu >= maxcpus) { + zend_argument_value_error(2, "cpu id must be between 0 and " ZEND_ULONG_FMT " (" ZEND_LONG_FMT ")", maxcpus, cpu); + RETURN_THROWS(); + } + + if (!CPU_ISSET(cpu, &mask)) { + CPU_SET(cpu, &mask); + } + } ZEND_HASH_FOREACH_END(); + + if (sched_setaffinity(pid, sizeof(mask), &mask) != 0) { + PCNTL_G(last_error) = errno; + switch (errno) { + case ESRCH: + zend_argument_value_error(1, "invalid process (" ZEND_LONG_FMT ")", pid); + RETURN_THROWS(); + case EPERM: + php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges"); + break; + default: + php_error_docref(NULL, E_WARNING, "Error %d", errno); + } + RETURN_FALSE; + } else { + RETURN_TRUE; + } +} +#endif + static void pcntl_interrupt_function(zend_execute_data *execute_data) { pcntl_signal_dispatch(); diff --git a/ext/pcntl/pcntl.stub.php b/ext/pcntl/pcntl.stub.php index a2420f335236d..da90057e437d0 100644 --- a/ext/pcntl/pcntl.stub.php +++ b/ext/pcntl/pcntl.stub.php @@ -994,3 +994,8 @@ function pcntl_forkx(int $flags): int{} #ifdef HAVE_PIDFD_OPEN function pcntl_setns(?int $process_id = null, int $nstype = CLONE_NEWNET): bool {} #endif + +#ifdef HAVE_SCHED_SETAFFINITY +function pcntl_getcpuaffinity(?int $process_id = null): array|false {} +function pcntl_setcpuaffinity(?int $process_id = null, array $cpu_ids = []): bool {} +#endif diff --git a/ext/pcntl/pcntl_arginfo.h b/ext/pcntl/pcntl_arginfo.h index cd16e4f19cd62..b9ac54657abbe 100644 --- a/ext/pcntl/pcntl_arginfo.h +++ b/ext/pcntl/pcntl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e5204cee68c41ff1201992f2572940c8f87980a3 */ + * Stub hash: a61b0327f5c36ca91e19c5f370377794b7950dee */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_fork, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -139,6 +139,19 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_setns, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() #endif +#if defined(HAVE_SCHED_SETAFFINITY) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_pcntl_getcpuaffinity, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, process_id, IS_LONG, 1, "null") +ZEND_END_ARG_INFO() +#endif + +#if defined(HAVE_SCHED_SETAFFINITY) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_setcpuaffinity, 0, 0, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, process_id, IS_LONG, 1, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, cpu_ids, IS_ARRAY, 0, "[]") +ZEND_END_ARG_INFO() +#endif + ZEND_FUNCTION(pcntl_fork); ZEND_FUNCTION(pcntl_waitpid); ZEND_FUNCTION(pcntl_wait); @@ -186,6 +199,12 @@ ZEND_FUNCTION(pcntl_forkx); #if defined(HAVE_PIDFD_OPEN) ZEND_FUNCTION(pcntl_setns); #endif +#if defined(HAVE_SCHED_SETAFFINITY) +ZEND_FUNCTION(pcntl_getcpuaffinity); +#endif +#if defined(HAVE_SCHED_SETAFFINITY) +ZEND_FUNCTION(pcntl_setcpuaffinity); +#endif static const zend_function_entry ext_functions[] = { ZEND_FE(pcntl_fork, arginfo_pcntl_fork) @@ -235,6 +254,12 @@ static const zend_function_entry ext_functions[] = { #endif #if defined(HAVE_PIDFD_OPEN) ZEND_FE(pcntl_setns, arginfo_pcntl_setns) +#endif +#if defined(HAVE_SCHED_SETAFFINITY) + ZEND_FE(pcntl_getcpuaffinity, arginfo_pcntl_getcpuaffinity) +#endif +#if defined(HAVE_SCHED_SETAFFINITY) + ZEND_FE(pcntl_setcpuaffinity, arginfo_pcntl_setcpuaffinity) #endif ZEND_FE_END }; diff --git a/ext/pcntl/tests/pcntl_cpuaffinity.phpt b/ext/pcntl/tests/pcntl_cpuaffinity.phpt new file mode 100644 index 0000000000000..6dc3399e16118 --- /dev/null +++ b/ext/pcntl/tests/pcntl_cpuaffinity.phpt @@ -0,0 +1,69 @@ +--TEST-- +pcntl_getcpuaffinity() and pcntl_setcpuaffinity() +--EXTENSIONS-- +pcntl +--SKIPIF-- + +--FILE-- +getMessage() . PHP_EOL; +} + +try { + pcntl_setcpuaffinity(null, ["abc" => "def", 0 => "cpuid"]); +} catch (\ValueError $e) { + echo $e->getMessage() . PHP_EOL; +} + +try { + pcntl_setcpuaffinity(null, [PHP_INT_MAX]); +} catch (\ValueError $e) { + echo $e->getMessage() . PHP_EOL; +} + +try { + pcntl_setcpuaffinity(null, [-1024, 64, -2]); +} catch (\ValueError $e) { + echo $e->getMessage() . PHP_EOL; +} + +try { + pcntl_getcpuaffinity(-1024); +} catch (\ValueError $e) { + echo $e->getMessage() . PHP_EOL; +} + +try { + pcntl_setcpuaffinity(null, [1, array(1)]); +} catch (\ValueError $e) { + echo $e->getMessage(); +} +?> +--EXPECTF-- +bool(true) +array(0) { +} +array(0) { +} +bool(true) +pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) must not be empty +pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id invalid value (def) +pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id must be between 0 and %d (%d) +pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id must be between 0 and %d (-1024) +pcntl_getcpuaffinity(): Argument #1 ($process_id) invalid process (-1024) + +Warning: Array to string conversion in %s on line %d +pcntl_setcpuaffinity(): Argument #2 ($cpu_ids) cpu id invalid type (Array)