Skip to content
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

SIGCHLD is not always returned from proc_open #11498

Closed
taka-oyama opened this issue Jun 21, 2023 · 17 comments
Closed

SIGCHLD is not always returned from proc_open #11498

taka-oyama opened this issue Jun 21, 2023 · 17 comments

Comments

@taka-oyama
Copy link

taka-oyama commented Jun 21, 2023

Description

The following code:

<?php

ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);

// store processes in an array as [$pid => $process, ...]
$processes = [];

// catch signals from child processes and unset processes from the array
pcntl_async_signals(true);
pcntl_signal(SIGCHLD, function($sig, $info) use (&$processes) {
    unset($processes[$info['pid']]);
}, false);

// open 10 processes and store them in an array
foreach (range(0, 10) as $i) {
    $process = proc_open('echo $$', [], $pipes); // prints the pid to STDOUT
    $pid = proc_get_status($process)['pid'];
    $processes[$pid] = $process;
}

// wait for all processes to exit
while(true) {
    echo ' processes remaining:' . count($processes) . PHP_EOL;
    if (empty($processes)) {
        break;
    }
    sleep(1);
}

// Check that all processes have been removed.
var_dump($processes);

Resulted in this output:

921
922
 processes remaining:9
925
926
924
 processes remaining:8
923
 processes remaining:6
931
929
927
 processes remaining:5
928
930
 processes remaining:2
 processes remaining:2
 processes remaining:2
 processes remaining:2
 processes remaining:2
 processes remaining:2
 processes remaining:2
 ...

But I expected this output instead:

837
838
 processes remaining:10
842
840
 processes remaining:8
 processes remaining:7
846
841
 processes remaining:6
847
 processes remaining:5
 processes remaining:4
839
843
 processes remaining:3
844
 processes remaining:1
845
 processes remaining:0
array(0) {
}

There were some rare cases where the script would exit with code 255 with no errors.

root@c09c0fa26c4d:/app# php test.php
933
934
936
 processes remaining:8
937
935
 processes remaining:7
938
939
941
 processes remaining:5
940
942
943
root@c09c0fa26c4d:/app# echo $?
255

PHP Version

PHP 8.2.7 (cli) (built: Jun 13 2023 23:05:53) (NTS)

Operating System

Debian GNU/Linux 12

@nielsdos
Copy link
Member

I have a feeling multiple signals get collapsed into one and a loop is missing somewhere to go over all of them.

@nielsdos
Copy link
Member

nielsdos commented Jun 21, 2023

Yep, that's definitely the issue. Here's a quick PoC patch that solves it for me (although testing is appreciated ^^).
Note that this patch is incomplete: there's still some optimisation opportunity + todos in the patch. However, I'm out of time for today...

diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c
index c2b7f390fc..82890eb407 100644
--- a/ext/pcntl/pcntl.c
+++ b/ext/pcntl/pcntl.c
@@ -1055,23 +1055,40 @@ static void pcntl_signal_handler(int signo)
 		/* oops, too many signals for us to track, so we'll forget about this one */
 		return;
 	}
-	PCNTL_G(spares) = psig->next;
 
-	psig->signo = signo;
-	psig->next = NULL;
+	struct php_pcntl_pending_signal *psig_first = psig;
+
+	/* May be a merged signal. */
+	do {
+		// TODO: we should only do the merge check for *standard* signals, otherwise we shouldn't loop.
+		int status;
+		pid_t pid = waitpid(-1, &status, WNOHANG);
+		if (pid <= 0) {
+			break;
+		}
+
+		psig->signo = signo;
 
 #ifdef HAVE_STRUCT_SIGINFO_T
-	psig->siginfo = *siginfo;
+		psig->siginfo = *siginfo;
+		psig->siginfo.si_pid = pid; // TODO: depends on the signal
 #endif
 
+		psig = psig->next;
+	} while (psig);
+
+	PCNTL_G(spares) = psig->next;
+	psig->next = NULL;
+
 	/* the head check is important, as the tick handler cannot atomically clear both
 	 * the head and tail */
 	if (PCNTL_G(head) && PCNTL_G(tail)) {
-		PCNTL_G(tail)->next = psig;
+		PCNTL_G(tail)->next = psig_first;
 	} else {
-		PCNTL_G(head) = psig;
+		PCNTL_G(head) = psig_first;
 	}
 	PCNTL_G(tail) = psig;
+
 	PCNTL_G(pending_signals) = 1;
 	if (PCNTL_G(async_signals)) {
 		zend_atomic_bool_store_ex(&EG(vm_interrupt), true);

@taka-oyama
Copy link
Author

taka-oyama commented Jun 22, 2023

Hi, thanks for looking into the issue. I tried out the patch and it seems to be working.
The script no longer hangs!

There is still ~30% chance that it ends prematurely and exits with code 255 though.

# php test.php; echo $?
3863
3864
 processes remaining:9
3868
3866
 processes remaining:7
3867
 processes remaining:6
3865
 processes remaining:5
3873
3869
3872
3870
3871
255

@nielsdos
Copy link
Member

I can't reproduce the exit status 255 problem.
But exit status 255 usually means there's an uncaught error somewhere in your code.
Can you try to trigger the issue with php -d error_reporting=-1 -d display_errors=1 test.php? By giving these options all errors should be displayed. Hopefully this will reveal something.
If that doesn't reveal anything, would you be able to recompile with ASAN support? i.e. pass in --enable-address-sanitizer as a ./configure option. You'll need to set the environment variable USE_ZEND_ALLOC=0 for that to work then though.

@taka-oyama
Copy link
Author

Hi, thanks for the detailed instructions.

I couldn't get the error to show up even with the options so I rebuilt PHP with ASAN support.
I was able to reproduce it after a few tries and got the following error.

=================================================================
==430==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 256 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55b734 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0xaaaad6aff020 in __zend_malloc /usr/src/php/Zend/zend_alloc.c:3114
    #2 0xaaaad6ad91b4 in zend_compile /usr/src/php/Zend/zend_language_scanner.c:607
    #3 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #4 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #5 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #6 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #7 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #8 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #9 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #10 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #11 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Direct leak of 224 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55a794 in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:85
    #1 0xaaaad6aff3b4 in __zend_realloc /usr/src/php/Zend/zend_alloc.c:3133
    #2 0xaaaad6b6220c in pass_two /usr/src/php/Zend/zend_opcode.c:1048
    #3 0xaaaad6b29510 in zend_compile_func_decl /usr/src/php/Zend/zend_compile.c:7447
    #4 0xaaaad6b2dab8 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10333
    #5 0xaaaad6b459c0 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #6 0xaaaad6b459c0 in zend_compile_args /usr/src/php/Zend/zend_compile.c:3612
    #7 0xaaaad6b459c0 in zend_compile_call_common /usr/src/php/Zend/zend_compile.c:3715
    #8 0xaaaad6b3e454 in zend_compile_call /usr/src/php/Zend/zend_compile.c:4509
    #9 0xaaaad6b3e454 in zend_compile_var_inner /usr/src/php/Zend/zend_compile.c:10369
    #10 0xaaaad6b3e454 in zend_compile_var /usr/src/php/Zend/zend_compile.c:10395
    #11 0xaaaad6b2e038 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10230
    #12 0xaaaad6b20400 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #13 0xaaaad6b20400 in zend_compile_stmt /usr/src/php/Zend/zend_compile.c:10192
    #14 0xaaaad6b1f1a0 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10078
    #15 0xaaaad6b1f2c8 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10064
    #16 0xaaaad6ad926c in zend_compile /usr/src/php/Zend/zend_language_scanner.c:620
    #17 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #18 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #19 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #20 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #21 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #22 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #23 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #24 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #25 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Direct leak of 64 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55b734 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0xaaaad6aff020 in __zend_malloc /usr/src/php/Zend/zend_alloc.c:3114
    #2 0xaaaad6b27d84 in zend_compile_params /usr/src/php/Zend/zend_compile.c:6739
    #3 0xaaaad6b27d84 in zend_compile_func_decl /usr/src/php/Zend/zend_compile.c:7403
    #4 0xaaaad6b2dab8 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10333
    #5 0xaaaad6b459c0 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #6 0xaaaad6b459c0 in zend_compile_args /usr/src/php/Zend/zend_compile.c:3612
    #7 0xaaaad6b459c0 in zend_compile_call_common /usr/src/php/Zend/zend_compile.c:3715
    #8 0xaaaad6b3e454 in zend_compile_call /usr/src/php/Zend/zend_compile.c:4509
    #9 0xaaaad6b3e454 in zend_compile_var_inner /usr/src/php/Zend/zend_compile.c:10369
    #10 0xaaaad6b3e454 in zend_compile_var /usr/src/php/Zend/zend_compile.c:10395
    #11 0xaaaad6b2e038 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10230
    #12 0xaaaad6b20400 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #13 0xaaaad6b20400 in zend_compile_stmt /usr/src/php/Zend/zend_compile.c:10192
    #14 0xaaaad6b1f1a0 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10078
    #15 0xaaaad6b1f2c8 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10064
    #16 0xaaaad6ad926c in zend_compile /usr/src/php/Zend/zend_language_scanner.c:620
    #17 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #18 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #19 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #20 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #21 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #22 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #23 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #24 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #25 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Direct leak of 56 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55b734 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0xaaaad6aff020 in __zend_malloc /usr/src/php/Zend/zend_alloc.c:3114
    #2 0xaaaad6ba6488 in _zend_new_array_0 /usr/src/php/Zend/zend_hash.c:274
    #3 0xaaaad6b2ab74 in zend_compile_closure_binding /usr/src/php/Zend/zend_compile.c:6998
    #4 0xaaaad6b2ab74 in zend_compile_func_decl /usr/src/php/Zend/zend_compile.c:7366
    #5 0xaaaad6b2dab8 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10333
    #6 0xaaaad6b459c0 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #7 0xaaaad6b459c0 in zend_compile_args /usr/src/php/Zend/zend_compile.c:3612
    #8 0xaaaad6b459c0 in zend_compile_call_common /usr/src/php/Zend/zend_compile.c:3715
    #9 0xaaaad6b3e454 in zend_compile_call /usr/src/php/Zend/zend_compile.c:4509
    #10 0xaaaad6b3e454 in zend_compile_var_inner /usr/src/php/Zend/zend_compile.c:10369
    #11 0xaaaad6b3e454 in zend_compile_var /usr/src/php/Zend/zend_compile.c:10395
    #12 0xaaaad6b2e038 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10230
    #13 0xaaaad6b20400 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #14 0xaaaad6b20400 in zend_compile_stmt /usr/src/php/Zend/zend_compile.c:10192
    #15 0xaaaad6b1f1a0 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10078
    #16 0xaaaad6b1f2c8 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10064
    #17 0xaaaad6ad926c in zend_compile /usr/src/php/Zend/zend_language_scanner.c:620
    #18 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #19 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #20 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #21 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #22 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #23 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #24 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #25 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #26 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Direct leak of 48 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55a794 in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:85
    #1 0xaaaad6aff3b4 in __zend_realloc /usr/src/php/Zend/zend_alloc.c:3133
    #2 0xaaaad6c4ed68 in zend_string_extend /usr/src/php/Zend/zend_string.h:249
    #3 0xaaaad6c4ed68 in ZEND_CONCAT_SPEC_TMPVAR_CONST_HANDLER /usr/src/php/Zend/zend_vm_execute.h:14947
    #4 0xaaaad6d137a4 in execute_ex /usr/src/php/Zend/zend_vm_execute.h:57565
    #5 0xaaaad6d3dedc in zend_execute /usr/src/php/Zend/zend_vm_execute.h:60396
    #6 0xaaaad6b81314 in zend_execute_scripts /usr/src/php/Zend/zend.c:1827
    #7 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #8 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #9 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #10 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #11 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #12 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Direct leak of 40 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55b734 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0xaaaad6aff020 in __zend_malloc /usr/src/php/Zend/zend_alloc.c:3114
    #2 0xaaaad6ad330c in zend_string_alloc /usr/src/php/Zend/zend_string.h:152
    #3 0xaaaad6ad330c in zend_string_init /usr/src/php/Zend/zend_string.h:174
    #4 0xaaaad6ad330c in zendparse /usr/src/php/Zend/zend_language_parser.c:6363
    #5 0xaaaad6ad9170 in zend_compile /usr/src/php/Zend/zend_language_scanner.c:601
    #6 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #7 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #8 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #9 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #10 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #11 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #12 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #13 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #14 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Direct leak of 24 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55a794 in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:85
    #1 0xaaaad6aff3b4 in __zend_realloc /usr/src/php/Zend/zend_alloc.c:3133
    #2 0xaaaad6b62620 in pass_two /usr/src/php/Zend/zend_opcode.c:1034
    #3 0xaaaad6b29510 in zend_compile_func_decl /usr/src/php/Zend/zend_compile.c:7447
    #4 0xaaaad6b2dab8 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10333
    #5 0xaaaad6b459c0 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #6 0xaaaad6b459c0 in zend_compile_args /usr/src/php/Zend/zend_compile.c:3612
    #7 0xaaaad6b459c0 in zend_compile_call_common /usr/src/php/Zend/zend_compile.c:3715
    #8 0xaaaad6b3e454 in zend_compile_call /usr/src/php/Zend/zend_compile.c:4509
    #9 0xaaaad6b3e454 in zend_compile_var_inner /usr/src/php/Zend/zend_compile.c:10369
    #10 0xaaaad6b3e454 in zend_compile_var /usr/src/php/Zend/zend_compile.c:10395
    #11 0xaaaad6b2e038 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10230
    #12 0xaaaad6b20400 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #13 0xaaaad6b20400 in zend_compile_stmt /usr/src/php/Zend/zend_compile.c:10192
    #14 0xaaaad6b1f1a0 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10078
    #15 0xaaaad6b1f2c8 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10064
    #16 0xaaaad6ad926c in zend_compile /usr/src/php/Zend/zend_language_scanner.c:620
    #17 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #18 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #19 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #20 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #21 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #22 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #23 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #24 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #25 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Direct leak of 8 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55b734 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0xaaaad6aff020 in __zend_malloc /usr/src/php/Zend/zend_alloc.c:3114
    #2 0xaaaad6b5d4ac in init_op_array /usr/src/php/Zend/zend_opcode.c:55
    #3 0xaaaad6b27498 in zend_compile_func_decl /usr/src/php/Zend/zend_compile.c:7339
    #4 0xaaaad6b2dab8 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10333
    #5 0xaaaad6b459c0 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #6 0xaaaad6b459c0 in zend_compile_args /usr/src/php/Zend/zend_compile.c:3612
    #7 0xaaaad6b459c0 in zend_compile_call_common /usr/src/php/Zend/zend_compile.c:3715
    #8 0xaaaad6b3e454 in zend_compile_call /usr/src/php/Zend/zend_compile.c:4509
    #9 0xaaaad6b3e454 in zend_compile_var_inner /usr/src/php/Zend/zend_compile.c:10369
    #10 0xaaaad6b3e454 in zend_compile_var /usr/src/php/Zend/zend_compile.c:10395
    #11 0xaaaad6b2e038 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10230
    #12 0xaaaad6b20400 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #13 0xaaaad6b20400 in zend_compile_stmt /usr/src/php/Zend/zend_compile.c:10192
    #14 0xaaaad6b1f1a0 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10078
    #15 0xaaaad6b1f2c8 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10064
    #16 0xaaaad6ad926c in zend_compile /usr/src/php/Zend/zend_language_scanner.c:620
    #17 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #18 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #19 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #20 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #21 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #22 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #23 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #24 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #25 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Indirect leak of 2352 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55a794 in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:85
    #1 0xaaaad6aff3b4 in __zend_realloc /usr/src/php/Zend/zend_alloc.c:3133
    #2 0xaaaad6b6220c in pass_two /usr/src/php/Zend/zend_opcode.c:1048
    #3 0xaaaad6ad92e8 in zend_compile /usr/src/php/Zend/zend_language_scanner.c:625
    #4 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #5 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #6 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #7 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #8 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #9 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #10 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #11 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #12 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Indirect leak of 320 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55b734 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0xaaaad6aff020 in __zend_malloc /usr/src/php/Zend/zend_alloc.c:3114
    #2 0xaaaad6ba740c in zend_hash_real_init_mixed_ex /usr/src/php/Zend/zend_hash.c:174
    #3 0xaaaad6ba740c in zend_hash_real_init_mixed /usr/src/php/Zend/zend_hash.c:333
    #4 0xaaaad6bac700 in _zend_hash_add_or_update_i /usr/src/php/Zend/zend_hash.c:762
    #5 0xaaaad6bac700 in zend_hash_add /usr/src/php/Zend/zend_hash.c:921
    #6 0xaaaad6b27a2c in zend_compile_closure_binding /usr/src/php/Zend/zend_compile.c:7016
    #7 0xaaaad6b27a2c in zend_compile_func_decl /usr/src/php/Zend/zend_compile.c:7366
    #8 0xaaaad6b2dab8 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10333
    #9 0xaaaad6b459c0 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #10 0xaaaad6b459c0 in zend_compile_args /usr/src/php/Zend/zend_compile.c:3612
    #11 0xaaaad6b459c0 in zend_compile_call_common /usr/src/php/Zend/zend_compile.c:3715
    #12 0xaaaad6b3e454 in zend_compile_call /usr/src/php/Zend/zend_compile.c:4509
    #13 0xaaaad6b3e454 in zend_compile_var_inner /usr/src/php/Zend/zend_compile.c:10369
    #14 0xaaaad6b3e454 in zend_compile_var /usr/src/php/Zend/zend_compile.c:10395
    #15 0xaaaad6b2e038 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10230
    #16 0xaaaad6b20400 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #17 0xaaaad6b20400 in zend_compile_stmt /usr/src/php/Zend/zend_compile.c:10192
    #18 0xaaaad6b1f1a0 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10078
    #19 0xaaaad6b1f2c8 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10064
    #20 0xaaaad6ad926c in zend_compile /usr/src/php/Zend/zend_language_scanner.c:620
    #21 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #22 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #23 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #24 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #25 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #26 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #27 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #28 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #29 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Indirect leak of 80 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55b734 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0xaaaad6aff020 in __zend_malloc /usr/src/php/Zend/zend_alloc.c:3114
    #2 0xaaaad6d3df70 in i_init_code_execute_data /usr/src/php/Zend/zend_execute.c:3974
    #3 0xaaaad6d3df70 in zend_execute /usr/src/php/Zend/zend_vm_execute.h:60394
    #4 0xaaaad6b81314 in zend_execute_scripts /usr/src/php/Zend/zend.c:1827
    #5 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #6 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #7 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #8 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #9 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #10 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Indirect leak of 40 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55a794 in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:85
    #1 0xaaaad6aff3b4 in __zend_realloc /usr/src/php/Zend/zend_alloc.c:3133
    #2 0xaaaad6b62620 in pass_two /usr/src/php/Zend/zend_opcode.c:1034
    #3 0xaaaad6ad92e8 in zend_compile /usr/src/php/Zend/zend_language_scanner.c:625
    #4 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #5 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #6 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #7 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #8 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #9 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #10 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #11 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #12 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Indirect leak of 40 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55b734 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0xaaaad6aff020 in __zend_malloc /usr/src/php/Zend/zend_alloc.c:3114
    #2 0xaaaad6a8190c in zend_string_alloc /usr/src/php/Zend/zend_string.h:152
    #3 0xaaaad6a8190c in zend_string_init /usr/src/php/Zend/zend_string.h:174
    #4 0xaaaad6a8190c in php_execute_script /usr/src/php/main/main.c:2521
    #5 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #6 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #7 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #8 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #9 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Indirect leak of 24 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55a794 in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:85
    #1 0xaaaad6aff3b4 in __zend_realloc /usr/src/php/Zend/zend_alloc.c:3133
    #2 0xaaaad6b5c36c in emit_live_range_raw /usr/src/php/Zend/zend_opcode.c:706
    #3 0xaaaad6b5d040 in zend_calc_live_ranges /usr/src/php/Zend/zend_opcode.c:941
    #4 0xaaaad6ad92e8 in zend_compile /usr/src/php/Zend/zend_language_scanner.c:625
    #5 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #6 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #7 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #8 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #9 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #10 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #11 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #12 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #13 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Indirect leak of 8 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55b734 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0xaaaad6aff020 in __zend_malloc /usr/src/php/Zend/zend_alloc.c:3114
    #2 0xaaaad6b5d4ac in init_op_array /usr/src/php/Zend/zend_opcode.c:55
    #3 0xaaaad6ad91c4 in zend_compile /usr/src/php/Zend/zend_language_scanner.c:608
    #4 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #5 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #6 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #7 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #8 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #9 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #10 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #11 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #12 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

Indirect leak of 8 byte(s) in 1 object(s) allocated from:
    #0 0xffff9a55a794 in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:85
    #1 0xaaaad6aff3b4 in __zend_realloc /usr/src/php/Zend/zend_alloc.c:3133
    #2 0xaaaad6b28898 in zend_add_dynamic_func_def /usr/src/php/Zend/zend_compile.c:7269
    #3 0xaaaad6b28898 in zend_begin_func_decl /usr/src/php/Zend/zend_compile.c:7306
    #4 0xaaaad6b28898 in zend_compile_func_decl /usr/src/php/Zend/zend_compile.c:7361
    #5 0xaaaad6b2dab8 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10333
    #6 0xaaaad6b459c0 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #7 0xaaaad6b459c0 in zend_compile_args /usr/src/php/Zend/zend_compile.c:3612
    #8 0xaaaad6b459c0 in zend_compile_call_common /usr/src/php/Zend/zend_compile.c:3715
    #9 0xaaaad6b3e454 in zend_compile_call /usr/src/php/Zend/zend_compile.c:4509
    #10 0xaaaad6b3e454 in zend_compile_var_inner /usr/src/php/Zend/zend_compile.c:10369
    #11 0xaaaad6b3e454 in zend_compile_var /usr/src/php/Zend/zend_compile.c:10395
    #12 0xaaaad6b2e038 in zend_compile_expr_inner /usr/src/php/Zend/zend_compile.c:10230
    #13 0xaaaad6b20400 in zend_compile_expr /usr/src/php/Zend/zend_compile.c:10350
    #14 0xaaaad6b20400 in zend_compile_stmt /usr/src/php/Zend/zend_compile.c:10192
    #15 0xaaaad6b1f1a0 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10078
    #16 0xaaaad6b1f2c8 in zend_compile_top_stmt /usr/src/php/Zend/zend_compile.c:10064
    #17 0xaaaad6ad926c in zend_compile /usr/src/php/Zend/zend_language_scanner.c:620
    #18 0xaaaad6adcf80 in compile_file /usr/src/php/Zend/zend_language_scanner.c:655
    #19 0xaaaad6803b28 in phar_compile_file /usr/src/php/ext/phar/phar.c:3355
    #20 0xaaaad6b812dc in zend_execute_scripts /usr/src/php/Zend/zend.c:1822
    #21 0xaaaad6a81658 in php_execute_script /usr/src/php/main/main.c:2542
    #22 0xaaaad6e7bacc in do_cli /usr/src/php/sapi/cli/php_cli.c:964
    #23 0xaaaad6530ad8 in main /usr/src/php/sapi/cli/php_cli.c:1333
    #24 0xffff99f4777c  (/lib/aarch64-linux-gnu/libc.so.6+0x2777c)
    #25 0xffff99f47854 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x27854)
    #26 0xaaaad6530dac in _start (/usr/local/bin/php+0x330dac)

SUMMARY: AddressSanitizer: 3592 byte(s) leaked in 16 allocation(s).

@nielsdos
Copy link
Member

I can reproduce the "error code 255" issue sometimes, it's very hard to trigger though, I need to repeatedly start the process in a tight loop. I'll try to debug that.

@nielsdos
Copy link
Member

Okay, partially found the reason it exits sapi_cli_single_write() fails which in sapi_cli_ub_write() sets the exit status to 255 and abort the program. Now the question is why the write fails...

@nielsdos
Copy link
Member

Bingo, a signal arrives during the write, which causes the return value to be -1. We handle EAGAIN already but we don't handle EINTR...
That's a separate issue though, so I'm gonna split the patch into two different PRs.

nielsdos added a commit to nielsdos/php-src that referenced this issue Jun 22, 2023
Linux, and maybe other unixes, may merge multiple standard signals into
a single one. This causes issues when keeping track of process IDs.
Solve this by manually checking which children are dead using waitpid().

Test case is based on taka-oyama's test code.
nielsdos added a commit to nielsdos/php-src that referenced this issue Jun 22, 2023
When writing the output in the CLI is interrupted by a signal, the
writing will fail in sapi_cli_single_write(), causing an exit later in
sapi_cli_ub_write(). This was the other part of the issue in phpGH-11498.
The solution is to restart the write if an EINTR has been observed.
nielsdos added a commit to nielsdos/php-src that referenced this issue Jun 22, 2023
When writing the output in the CLI is interrupted by a signal, the
writing will fail in sapi_cli_single_write(), causing an exit later in
sapi_cli_ub_write(). This was the other part of the issue in phpGH-11498.
The solution is to restart the write if an EINTR has been observed.
@taka-oyama
Copy link
Author

Thank you for the fixes!

nielsdos added a commit to nielsdos/php-src that referenced this issue Jun 23, 2023
Linux, and maybe other unixes, may merge multiple standard signals into
a single one. This causes issues when keeping track of process IDs.
Solve this by manually checking which children are dead using waitpid().

Test case is based on taka-oyama's test code.
nielsdos added a commit that referenced this issue Jun 23, 2023
When writing the output in the CLI is interrupted by a signal, the
writing will fail in sapi_cli_single_write(), causing an exit later in
sapi_cli_ub_write(). This was the other part of the issue in GH-11498.
The solution is to restart the write if an EINTR has been observed.

Closes GH-11510.
nielsdos added a commit that referenced this issue Jun 23, 2023
* PHP-8.1:
  Fix GH-11498: SIGCHLD is not always returned from proc_open
nielsdos added a commit that referenced this issue Jun 23, 2023
* PHP-8.2:
  Fix GH-11498: SIGCHLD is not always returned from proc_open
nielsdos added a commit to nielsdos/php-src that referenced this issue Aug 2, 2023
People relied on manually waiting for children, but the fix for phpGH-11498
broke this. Fixing this in PHP is fundamentally incompatible with doing
the wait loop in userland. This reverts to the old behaviour.
nielsdos added a commit to nielsdos/php-src that referenced this issue Aug 2, 2023
People relied on manually waiting for children, but the fix for phpGH-11498
broke this. Fixing this in PHP is fundamentally incompatible with doing
the wait loop in userland. This reverts to the old behaviour.
nielsdos added a commit that referenced this issue Aug 3, 2023
People relied on manually waiting for children, but the fix for GH-11498
broke this. Fixing this in PHP is fundamentally incompatible with doing
the wait loop in userland. This reverts to the old behaviour.

Closes GH-11863.
nielsdos added a commit that referenced this issue Aug 3, 2023
* PHP-8.1:
  Revert the fix for GH-11498
nielsdos added a commit that referenced this issue Aug 3, 2023
* PHP-8.2:
  Revert the fix for GH-11498
@nielsdos
Copy link
Member

nielsdos commented Aug 3, 2023

@taka-oyama The fix for this issue caused BC breaks for other people. We have therefore decided to revert this. This aligns the behaviour of PCNTL to the exact behaviour of the OS.
It is possible to handle all children as shown by the new test:

while (($pid = pcntl_wait($status, WUNTRACED | WNOHANG)) > 0) {
echo "SIGCHLD\n";
unset($processes[$pid]);
}

Note however, that the "exit 255" problem you experienced is still fixed.

@taka-oyama
Copy link
Author

Hi, thanks I will give that a try.

jorgsowa pushed a commit to jorgsowa/php-src that referenced this issue Aug 16, 2023
People relied on manually waiting for children, but the fix for phpGH-11498
broke this. Fixing this in PHP is fundamentally incompatible with doing
the wait loop in userland. This reverts to the old behaviour.

Closes phpGH-11863.
@taka-oyama
Copy link
Author

taka-oyama commented Aug 19, 2023

Hi @nielsdos, I tried the code you provided and I confirmed that it works. Thank you for the workaround.

However, I noticed that pcntl_wait does not provide any way of getting $siginfo that pcntl_signal's handler provides. Is there any way to get that?

@nielsdos
Copy link
Member

If you perform the pcntl_wait inside the signal handler you can reuse the relevant fields, most fields should be the same for all children.

@taka-oyama
Copy link
Author

taka-oyama commented Aug 19, 2023

I'm not sure that you can reuse the status field, since that contains the exit code for each process...?

If you run the code below, the exit codes don't match.

<?php

pcntl_async_signals(true);
pcntl_signal(SIGCHLD, function($sig, $info) {
    usleep(100);
    while(($pid = pcntl_wait($status, WUNTRACED | WNOHANG)) > 0) {
        echo 'SIGCHLD WUNTRACED: ' . $pid . ' exit code: ' . $info['status'] . PHP_EOL;
    }
}, false);

foreach (range(0, 100) as $i) {
    // exit proc with exit code: $i
    proc_open('echo "$$ exiting with code: ' . $i . '" && exit ' . $i, [], $pipes);
}

@taka-oyama
Copy link
Author

Sorry, I just noticed you can get the exit code by passing $status into pcntl_wexitstatus.

@nielsdos
Copy link
Member

The kernel doesn't give us the siginfo_t, that information is lost because of the signal merging it performs. You can indeed get the status via the pcntl function.

@taka-oyama
Copy link
Author

taka-oyama commented Aug 20, 2023

Ok. Understood. Thank you for replying 🙂.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants