From 8078a9be7240d337acfbe76434dfaef732c81542 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 11 Apr 2024 21:14:22 +0100 Subject: [PATCH 1/3] ext/pcntl: pcntl_getqos_class/pcntl_setqos_class addition. Introducting macOs Quality Of Service through those two calls. on macOs arm64/M*, there is no concept of individual cores, thus the old thread policy for cpu affinity does not work here. Instead, the user can apply to the current process the level of performance/energy consumption they wish from the highest QOS_CLASS_USER_INTERACTIVE to QOS_CLASS_BACKGROUND. --- ext/pcntl/config.m4 | 2 +- ext/pcntl/pcntl.c | 57 ++++++++++++++++++++++++- ext/pcntl/pcntl.stub.php | 15 +++++++ ext/pcntl/pcntl_arginfo.h | 64 ++++++++++++++++++++++++++++- ext/pcntl/tests/pcntl_qosclass.phpt | 26 ++++++++++++ 5 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 ext/pcntl/tests/pcntl_qosclass.phpt diff --git a/ext/pcntl/config.m4 b/ext/pcntl/config.m4 index c4e70a50039de..073148a0a1d48 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 sched_setaffinity]) + AC_CHECK_FUNCS([getpriority setpriority wait3 wait4 sigwaitinfo sigtimedwait unshare rfork forkx pidfd_open sched_setaffinity pthread_set_qos_class_self_np]) dnl if unsupported, -1 means automatically ENOSYS in this context AC_MSG_CHECKING([if sched_getcpu is supported]) diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index 6ccfd71f9ab62..c6d25687d9828 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -51,6 +51,11 @@ typedef cpuset_t cpu_set_t; #endif #endif +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) +#include +static zend_class_entry *QosClass_ce; +#endif + #ifdef HAVE_PIDFD_OPEN #include #endif @@ -65,10 +70,11 @@ typedef cpuset_t cpu_set_t; #define LONG_CONST(c) (zend_long) c -#include "pcntl_arginfo.h" - +#include "Zend/zend_enum.h" #include "Zend/zend_max_execution_timer.h" +#include "pcntl_arginfo.h" + ZEND_DECLARE_MODULE_GLOBALS(pcntl) static PHP_GINIT_FUNCTION(pcntl); @@ -136,6 +142,9 @@ PHP_RINIT_FUNCTION(pcntl) PHP_MINIT_FUNCTION(pcntl) { +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) + QosClass_ce = register_class_QosClass(); +#endif register_pcntl_symbols(module_number); orig_interrupt_function = zend_interrupt_function; zend_interrupt_function = pcntl_interrupt_function; @@ -1622,6 +1631,50 @@ PHP_FUNCTION(pcntl_getcpu) } #endif +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) +PHP_FUNCTION(pcntl_getqos_class) +{ + qos_class_t qos_class; + zend_object *qos_obj; + + ZEND_PARSE_PARAMETERS_NONE(); + + if (UNEXPECTED(pthread_get_qos_class_np(pthread_self(), &qos_class, NULL) != 0)) + { + // unlikely unless an external tool set the QOS class with a wrong value + PCNTL_G(last_error) = errno; + zend_throw_error(NULL, "invalid QOS class %u", qos_class); + RETURN_THROWS(); + } + + if (UNEXPECTED(zend_enum_get_case_by_value(&qos_obj, QosClass_ce, (zend_long)qos_class, NULL, false) == FAILURE)) + { + zend_throw_error(NULL, "invalid QOS value class entry %u", qos_class); + RETURN_THROWS(); + } + + RETVAL_OBJ(qos_obj); +} + +PHP_FUNCTION(pcntl_setqos_class) +{ + zval *qos_obj; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(qos_obj, QosClass_ce) + ZEND_PARSE_PARAMETERS_END(); + + zend_long qos_class = Z_LVAL_P(zend_enum_fetch_case_value(Z_OBJ_P(qos_obj))); + + if (pthread_set_qos_class_self_np((qos_class_t)qos_class, 0) != 0) + { + PCNTL_G(last_error) = errno; + zend_argument_value_error(1, "must be one of QosClass enum entries : ::UserInteractive, ::UserInitiated, ::Default, ::Utility or ::Background"); + RETURN_THROWS(); + } +} +#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 39f9db2d8b91c..de2caf9324f44 100644 --- a/ext/pcntl/pcntl.stub.php +++ b/ext/pcntl/pcntl.stub.php @@ -1003,3 +1003,18 @@ function pcntl_setcpuaffinity(?int $process_id = null, array $cpu_ids = []): boo #ifdef HAVE_SCHED_GETCPU function pcntl_getcpu(): int {} #endif + +#ifdef HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP +enum QosClass: int +{ + case UserInteractive = 0x21; + case UserInitiated = 0x19; + case Default = 0x15; + case Utility = 0x11; + case Background = 0x09; + case Unspecified = 0x00; +} + +function pcntl_getqos_class(): QosClass {} +function pcntl_setqos_class(QosClass $qos_class = QosClass::Default): void {} +#endif diff --git a/ext/pcntl/pcntl_arginfo.h b/ext/pcntl/pcntl_arginfo.h index c51128f1a8a98..127fdc2407d94 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: 75eacf08a17e18c30fb2111bb742c36b18aa9ead */ + * Stub hash: fdd48ba3c71ec80eb3ae740c5a6b8539efcdaa6c */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_fork, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -157,6 +157,17 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_getcpu, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() #endif +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_pcntl_getqos_class, 0, 0, QosClass, 0) +ZEND_END_ARG_INFO() +#endif + +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_setqos_class, 0, 0, IS_VOID, 0) + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, qos_class, QosClass, 0, "QosClass::Default") +ZEND_END_ARG_INFO() +#endif + ZEND_FUNCTION(pcntl_fork); ZEND_FUNCTION(pcntl_waitpid); ZEND_FUNCTION(pcntl_wait); @@ -213,6 +224,12 @@ ZEND_FUNCTION(pcntl_setcpuaffinity); #if defined(HAVE_SCHED_GETCPU) ZEND_FUNCTION(pcntl_getcpu); #endif +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) +ZEND_FUNCTION(pcntl_getqos_class); +#endif +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) +ZEND_FUNCTION(pcntl_setqos_class); +#endif static const zend_function_entry ext_functions[] = { ZEND_FE(pcntl_fork, arginfo_pcntl_fork) @@ -271,10 +288,22 @@ static const zend_function_entry ext_functions[] = { #endif #if defined(HAVE_SCHED_GETCPU) ZEND_FE(pcntl_getcpu, arginfo_pcntl_getcpu) +#endif +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) + ZEND_FE(pcntl_getqos_class, arginfo_pcntl_getqos_class) +#endif +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) + ZEND_FE(pcntl_setqos_class, arginfo_pcntl_setqos_class) #endif ZEND_FE_END }; +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) +static const zend_function_entry class_QosClass_methods[] = { + ZEND_FE_END +}; +#endif + static void register_pcntl_symbols(int module_number) { #if defined(WNOHANG) @@ -628,3 +657,36 @@ static void register_pcntl_symbols(int module_number) REGISTER_LONG_CONSTANT("PCNTL_ECAPMODE", ECAPMODE, CONST_PERSISTENT); #endif } + +#if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) +static zend_class_entry *register_class_QosClass(void) +{ + zend_class_entry *class_entry = zend_register_internal_enum("QosClass", IS_LONG, class_QosClass_methods); + + zval enum_case_UserInteractive_value; + ZVAL_LONG(&enum_case_UserInteractive_value, 0x21); + zend_enum_add_case_cstr(class_entry, "UserInteractive", &enum_case_UserInteractive_value); + + zval enum_case_UserInitiated_value; + ZVAL_LONG(&enum_case_UserInitiated_value, 0x19); + zend_enum_add_case_cstr(class_entry, "UserInitiated", &enum_case_UserInitiated_value); + + zval enum_case_Default_value; + ZVAL_LONG(&enum_case_Default_value, 0x15); + zend_enum_add_case_cstr(class_entry, "Default", &enum_case_Default_value); + + zval enum_case_Utility_value; + ZVAL_LONG(&enum_case_Utility_value, 0x11); + zend_enum_add_case_cstr(class_entry, "Utility", &enum_case_Utility_value); + + zval enum_case_Background_value; + ZVAL_LONG(&enum_case_Background_value, 0x9); + zend_enum_add_case_cstr(class_entry, "Background", &enum_case_Background_value); + + zval enum_case_Unspecified_value; + ZVAL_LONG(&enum_case_Unspecified_value, 0x0); + zend_enum_add_case_cstr(class_entry, "Unspecified", &enum_case_Unspecified_value); + + return class_entry; +} +#endif diff --git a/ext/pcntl/tests/pcntl_qosclass.phpt b/ext/pcntl/tests/pcntl_qosclass.phpt new file mode 100644 index 0000000000000..46aed88c3f23c --- /dev/null +++ b/ext/pcntl/tests/pcntl_qosclass.phpt @@ -0,0 +1,26 @@ +--TEST-- +pcntl_getqos_class()/pcntl_setqos_class() +--EXTENSIONS-- +pcntl +--SKIPIF-- + +--FILE-- +getMessage() . PHP_EOL; +} +pcntl_setqos_class(QosClass::Background); +var_dump(QosClass::Background == pcntl_getqos_class()); +?> +--EXPECT-- +bool(true) +pcntl_setqos_class(): Argument #1 ($qos_class) must be one of QosClass enum entries : ::UserInteractive, ::UserInitiated, ::Default, ::Utility or ::Background +bool(true) From ab885d81415c82744228a0a46dfe60f4586dd9a6 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 14 Apr 2024 23:12:32 +0100 Subject: [PATCH 2/3] changes to undef enums types. --- ext/pcntl/pcntl.c | 60 +++++++++++++++++++++++------ ext/pcntl/pcntl.stub.php | 13 +++---- ext/pcntl/pcntl_arginfo.h | 28 ++++---------- ext/pcntl/tests/pcntl_qosclass.phpt | 7 ---- 4 files changed, 62 insertions(+), 46 deletions(-) diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index c6d25687d9828..bf61071a322f9 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -1632,10 +1632,53 @@ PHP_FUNCTION(pcntl_getcpu) #endif #if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) +static qos_class_t qos_zval_to_lval(const zval *qos_obj) +{ + qos_class_t qos_class = QOS_CLASS_DEFAULT; + zend_string *entry = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(qos_obj))); + + if (zend_string_equals_cstr(entry, "UserInteractive", sizeof("UserInteractive") - 1)) { + qos_class = QOS_CLASS_USER_INTERACTIVE; + } else if (zend_string_equals_cstr(entry, "UserInitiated", sizeof("UserInitiated") - 1)) { + qos_class = QOS_CLASS_USER_INITIATED; + } else if (zend_string_equals_cstr(entry, "Utility", sizeof("Utility") - 1)) { + qos_class = QOS_CLASS_UTILITY; + } else if (zend_string_equals_cstr(entry, "Background", sizeof("Background") - 1)) { + qos_class = QOS_CLASS_BACKGROUND; + } + + return qos_class; +} + +static zend_object *qos_lval_to_zval(qos_class_t qos_class) +{ + const char *entryname; + switch (qos_class) + { + case QOS_CLASS_USER_INTERACTIVE: + entryname = "UserInteractive"; + break; + case QOS_CLASS_USER_INITIATED: + entryname = "UserInitiated"; + break; + case QOS_CLASS_UTILITY: + entryname = "Utility"; + break; + case QOS_CLASS_BACKGROUND: + entryname = "Background"; + break; + case QOS_CLASS_DEFAULT: + default: + entryname = "Default"; + break; + } + + return zend_enum_get_case_cstr(QosClass_ce, entryname); +} + PHP_FUNCTION(pcntl_getqos_class) { qos_class_t qos_class; - zend_object *qos_obj; ZEND_PARSE_PARAMETERS_NONE(); @@ -1647,13 +1690,7 @@ PHP_FUNCTION(pcntl_getqos_class) RETURN_THROWS(); } - if (UNEXPECTED(zend_enum_get_case_by_value(&qos_obj, QosClass_ce, (zend_long)qos_class, NULL, false) == FAILURE)) - { - zend_throw_error(NULL, "invalid QOS value class entry %u", qos_class); - RETURN_THROWS(); - } - - RETVAL_OBJ(qos_obj); + RETURN_OBJ_COPY(qos_lval_to_zval(qos_class)); } PHP_FUNCTION(pcntl_setqos_class) @@ -1664,12 +1701,13 @@ PHP_FUNCTION(pcntl_setqos_class) Z_PARAM_OBJECT_OF_CLASS(qos_obj, QosClass_ce) ZEND_PARSE_PARAMETERS_END(); - zend_long qos_class = Z_LVAL_P(zend_enum_fetch_case_value(Z_OBJ_P(qos_obj))); + qos_class_t qos_class = qos_zval_to_lval(qos_obj); - if (pthread_set_qos_class_self_np((qos_class_t)qos_class, 0) != 0) + if (UNEXPECTED(pthread_set_qos_class_self_np((qos_class_t)qos_class, 0) != 0)) { + // unlikely, unless it is a new os issue, as we draw from the specified enum values PCNTL_G(last_error) = errno; - zend_argument_value_error(1, "must be one of QosClass enum entries : ::UserInteractive, ::UserInitiated, ::Default, ::Utility or ::Background"); + zend_throw_error(NULL, "pcntl_setqos_class failed"); RETURN_THROWS(); } } diff --git a/ext/pcntl/pcntl.stub.php b/ext/pcntl/pcntl.stub.php index de2caf9324f44..cf1b5a4cced5f 100644 --- a/ext/pcntl/pcntl.stub.php +++ b/ext/pcntl/pcntl.stub.php @@ -1005,14 +1005,13 @@ function pcntl_getcpu(): int {} #endif #ifdef HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP -enum QosClass: int +enum QosClass { - case UserInteractive = 0x21; - case UserInitiated = 0x19; - case Default = 0x15; - case Utility = 0x11; - case Background = 0x09; - case Unspecified = 0x00; + case UserInteractive; + case UserInitiated; + case Default; + case Utility; + case Background; } function pcntl_getqos_class(): QosClass {} diff --git a/ext/pcntl/pcntl_arginfo.h b/ext/pcntl/pcntl_arginfo.h index 127fdc2407d94..734fb3976e0af 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: fdd48ba3c71ec80eb3ae740c5a6b8539efcdaa6c */ + * Stub hash: 3e15bebb568e6e2031acbd932d6eefbd23984c83 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pcntl_fork, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -661,31 +661,17 @@ static void register_pcntl_symbols(int module_number) #if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP) static zend_class_entry *register_class_QosClass(void) { - zend_class_entry *class_entry = zend_register_internal_enum("QosClass", IS_LONG, class_QosClass_methods); + zend_class_entry *class_entry = zend_register_internal_enum("QosClass", IS_UNDEF, class_QosClass_methods); - zval enum_case_UserInteractive_value; - ZVAL_LONG(&enum_case_UserInteractive_value, 0x21); - zend_enum_add_case_cstr(class_entry, "UserInteractive", &enum_case_UserInteractive_value); + zend_enum_add_case_cstr(class_entry, "UserInteractive", NULL); - zval enum_case_UserInitiated_value; - ZVAL_LONG(&enum_case_UserInitiated_value, 0x19); - zend_enum_add_case_cstr(class_entry, "UserInitiated", &enum_case_UserInitiated_value); + zend_enum_add_case_cstr(class_entry, "UserInitiated", NULL); - zval enum_case_Default_value; - ZVAL_LONG(&enum_case_Default_value, 0x15); - zend_enum_add_case_cstr(class_entry, "Default", &enum_case_Default_value); + zend_enum_add_case_cstr(class_entry, "Default", NULL); - zval enum_case_Utility_value; - ZVAL_LONG(&enum_case_Utility_value, 0x11); - zend_enum_add_case_cstr(class_entry, "Utility", &enum_case_Utility_value); + zend_enum_add_case_cstr(class_entry, "Utility", NULL); - zval enum_case_Background_value; - ZVAL_LONG(&enum_case_Background_value, 0x9); - zend_enum_add_case_cstr(class_entry, "Background", &enum_case_Background_value); - - zval enum_case_Unspecified_value; - ZVAL_LONG(&enum_case_Unspecified_value, 0x0); - zend_enum_add_case_cstr(class_entry, "Unspecified", &enum_case_Unspecified_value); + zend_enum_add_case_cstr(class_entry, "Background", NULL); return class_entry; } diff --git a/ext/pcntl/tests/pcntl_qosclass.phpt b/ext/pcntl/tests/pcntl_qosclass.phpt index 46aed88c3f23c..61b4259288697 100644 --- a/ext/pcntl/tests/pcntl_qosclass.phpt +++ b/ext/pcntl/tests/pcntl_qosclass.phpt @@ -11,16 +11,9 @@ if (getenv('SKIP_REPEAT')) die("skip Not repeatable"); getMessage() . PHP_EOL; -} pcntl_setqos_class(QosClass::Background); var_dump(QosClass::Background == pcntl_getqos_class()); ?> --EXPECT-- bool(true) -pcntl_setqos_class(): Argument #1 ($qos_class) must be one of QosClass enum entries : ::UserInteractive, ::UserInitiated, ::Default, ::Utility or ::Background bool(true) From 540c027aef061eea17ea0e3467fb1b320efdd53a Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 15 Apr 2024 17:39:54 +0100 Subject: [PATCH 3/3] use zend_string_equals_literal --- ext/pcntl/pcntl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index bf61071a322f9..1c09253f8dfd5 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -1637,13 +1637,13 @@ static qos_class_t qos_zval_to_lval(const zval *qos_obj) qos_class_t qos_class = QOS_CLASS_DEFAULT; zend_string *entry = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(qos_obj))); - if (zend_string_equals_cstr(entry, "UserInteractive", sizeof("UserInteractive") - 1)) { + if (zend_string_equals_literal(entry, "UserInteractive")) { qos_class = QOS_CLASS_USER_INTERACTIVE; - } else if (zend_string_equals_cstr(entry, "UserInitiated", sizeof("UserInitiated") - 1)) { + } else if (zend_string_equals_literal(entry, "UserInitiated")) { qos_class = QOS_CLASS_USER_INITIATED; - } else if (zend_string_equals_cstr(entry, "Utility", sizeof("Utility") - 1)) { + } else if (zend_string_equals_literal(entry, "Utility")) { qos_class = QOS_CLASS_UTILITY; - } else if (zend_string_equals_cstr(entry, "Background", sizeof("Background") - 1)) { + } else if (zend_string_equals_literal(entry, "Background")) { qos_class = QOS_CLASS_BACKGROUND; }