]> granicus.if.org Git - php/commitdiff
Honor script time limit when calling shutdown functions
authorAlex Dowad <alexinbeijing@gmail.com>
Wed, 6 May 2020 20:02:57 +0000 (22:02 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Wed, 13 May 2020 10:47:12 +0000 (12:47 +0200)
A time limit can be set on PHP script execution via `set_time_limit` (or .ini file).
When the time limit is reached, the OS will notify PHP and `timed_out` and `vm_interrupt`
flags are set. While these flags are regularly checked when executing PHP code, once the
end of the script is reached, they are not checked while invoking shutdown functions
(registered via `register_shutdown_function`).

Of course, if the shutdown functions are implemented *in* PHP, then the interrupt flag
will be checked while the VM is running PHP bytecode and the timeout will take effect.
But if the shutdown functions are built-in (implemented in C), it will not.

Since the shutdown functions are invoked through `zend_call_function`, add a check of the
`vm_interrupt` flag there. Then, the script time limit will be respected when *entering*
each shutdown function. The fact still remains that if a shutdown function is built-in and
runs for a long time, script execution will not time out until it finishes and the
interpreter tries to invoke the next one.

Still, the behavior of scripts with execution time limits will be more consistent after
this patch. To make the execution time-out feature work even more precisely, it would
be necessary to scrutinize all the built-in functions and add checks of the `vm_interrupt`
flag in any which can run for a long time. That might not be worth the effort, though.

It should be mentioned that this patch does not solely affect shutdown functions, neither
does it solely allow for interruption of running code due to script execution timeout.
Anything else which causes `vm_interrupt` to be set, such as the PHP interpreter receiving
a signal, will take effect when exiting from an internal function. And not just internal
functions which are called because they were registered to run at shutdown; there are
other cases where a series of internal functions might run in the midst of a script. In
all such cases, it will be possible to interrupt the interpreter now.

Closes GH-5543.

Zend/zend_execute_API.c
ext/pcntl/tests/async_signals_2.phpt [new file with mode: 0644]
tests/basic/timeout_variation_10.phpt
tests/basic/timeout_variation_9.phpt

index e8b70840b7831ba533eb0911fcb74d1f80ba29de..0c3917163d80d805c71ce20e7e6e6af25e9fac04 100644 (file)
@@ -830,6 +830,17 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
                        /* We must re-initialize function again */
                        fci_cache->function_handler = NULL;
                }
+
+               /* This flag is regularly checked while running user functions, but not internal
+                * So see whether interrupt flag was set while the function was running... */
+               if (EG(vm_interrupt)) {
+                       EG(vm_interrupt) = 0;
+                       if (EG(timed_out)) {
+                               zend_timeout();
+                       } else if (zend_interrupt_function) {
+                               zend_interrupt_function(EG(current_execute_data));
+                       }
+               }
        }
 
        zend_vm_stack_free_call_frame(call);
diff --git a/ext/pcntl/tests/async_signals_2.phpt b/ext/pcntl/tests/async_signals_2.phpt
new file mode 100644 (file)
index 0000000..be631ba
--- /dev/null
@@ -0,0 +1,29 @@
+--TEST--
+Async signals in zend_call_function
+--SKIPIF--
+<?php
+if (!extension_loaded("pcntl")) print "skip";
+if (getenv("SKIP_SLOW_TESTS")) print "skip slow test";
+?>
+--FILE--
+<?php
+
+pcntl_async_signals(1);
+pcntl_signal(SIGALRM, function($signo) {
+    throw new Exception("Alarm!");
+});
+
+pcntl_alarm(1);
+try {
+    array_map(
+        'time_nanosleep',
+        array_fill(0, 360, 1),
+        array_fill(0, 360, 0)
+    );
+} catch (Exception $e) {
+    echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Alarm!
index b067238db500e7ef911641cbf05a0ccf7b92d4d2..7680c96adf781d5c37f615a573a40d2db8f9cfe6 100644 (file)
@@ -5,8 +5,6 @@ Timeout within shutdown function, variation
 if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
 if (PHP_OS_FAMILY !== "Windows") die("skip Windows only test");
 ?>
---XFAIL--
-Missing timeout check in call_user_function
 --FILE--
 <?php
 
@@ -19,4 +17,5 @@ register_shutdown_function("sleep", 1);
 shutdown happens after here
 --EXPECTF--
 shutdown happens after here
+
 Fatal error: Maximum execution time of 1 second exceeded in %s on line %d
index e59ae242dc83b64c533b6bbed3b4789378630b03..d645ef2bbf445386a773d3535efe8153cb07d3e2 100644 (file)
@@ -5,8 +5,6 @@ Timeout within shutdown function
 if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
 if (PHP_OS_FAMILY !== "Windows") die("skip Windows only test");
 ?>
---XFAIL--
-Missing timeout check in call_user_function
 --FILE--
 <?php