[PHP-DEV] Switching max_execution_time from CPU time to wall-clock time and from SIGPROF to SIGALRM

Hi internals,

There is an issue with max_execution_time on MacOS, probably only
MacOS 14 on Apple Silicon, that causes timeouts to fire too early [1].
max_execution_time is implemented with setitimer(ITIMER_PROF) on this
platform, and the SIGPROF signal is delivered too early for some
reason. Switching to ITIMER_REAL appears to fix the issue, and Manuel
Kress opened a PR [3].

Are there objections against switching to ITIMER_REAL on MacOS (Apple
Silicon only) in all currently supported PHP versions? (next 8.2.x,
8.3.x, 8.x)

Apart from fixing the issue, this would introduce the following minor
breaking changes:

- max_execution_time on MacOS would be based on wall-clock time rather
than CPU time, so the time spent in I/O, sleep(), and syscalls in
general would count towards the max_execution_time
- The SIGALRM signal would be used instead of the SIGPROF signal
(using SIGALRM conflicts with pcntl_alarm(), but SIGPROF conflicts
with profilers). As noted by Dmitry, it also conflicts with sleep() on
some platforms, however this should be safe on MacOS.

Currently max_execution_time is based on wall-clock time on ZTS and
Windows, and CPU time otherwise. On Linux and FreeBSD, wall-clock time
can also be used when building with
--enable-zend-max-execution-timers. Máté proposed to add a wall-clock
based timeout in the past [2] but the discussion has stalled. Any
thoughts about eventually switching other platforms to wall-clock
timeouts in the next 8.x ?

TL;DR:
- Any objection about using wall-clock max_execution_time and SIGALRM
on MacOS Apple Silicon in all supported versions?
- Thoughts about using wall-clock timeouts on all platforms in the next 8.x ?

[1] `max_execution_time` reached too early · Issue #12814 · php/php-src · GitHub
[2] Add support for limiting maximum execution time based on wall-time by kocsismate · Pull Request #6504 · php/php-src · GitHub
[3] Use `ITIMER_REAL` for timeout handling on Apple Silicon system by windaishi · Pull Request #13567 · php/php-src · GitHub

Best Regards,
Arnaud

On Fri, May 17, 2024 at 3:50 PM Arnaud Le Blanc <arnaud.lb@gmail.com> wrote:

Hi internals,

There is an issue with max_execution_time on MacOS, probably only
MacOS 14 on Apple Silicon, that causes timeouts to fire too early [1].
max_execution_time is implemented with setitimer(ITIMER_PROF) on this
platform, and the SIGPROF signal is delivered too early for some
reason. Switching to ITIMER_REAL appears to fix the issue, and Manuel
Kress opened a PR [3].

Are there objections against switching to ITIMER_REAL on MacOS (Apple
Silicon only) in all currently supported PHP versions? (next 8.2.x,
8.3.x, 8.x)

Apart from fixing the issue, this would introduce the following minor
breaking changes:

- max_execution_time on MacOS would be based on wall-clock time rather
than CPU time, so the time spent in I/O, sleep(), and syscalls in
general would count towards the max_execution_time
- The SIGALRM signal would be used instead of the SIGPROF signal
(using SIGALRM conflicts with pcntl_alarm(), but SIGPROF conflicts
with profilers). As noted by Dmitry, it also conflicts with sleep() on
some platforms, however this should be safe on MacOS.

Currently max_execution_time is based on wall-clock time on ZTS and
Windows, and CPU time otherwise. On Linux and FreeBSD, wall-clock time
can also be used when building with
--enable-zend-max-execution-timers. Máté proposed to add a wall-clock
based timeout in the past [2] but the discussion has stalled. Any
thoughts about eventually switching other platforms to wall-clock
timeouts in the next 8.x ?

TL;DR:
- Any objection about using wall-clock max_execution_time and SIGALRM
on MacOS Apple Silicon in all supported versions?
- Thoughts about using wall-clock timeouts on all platforms in the next 8.x ?

[1] `max_execution_time` reached too early · Issue #12814 · php/php-src · GitHub
[2] Add support for limiting maximum execution time based on wall-time by kocsismate · Pull Request #6504 · php/php-src · GitHub
[3] Use `ITIMER_REAL` for timeout handling on Apple Silicon system by windaishi · Pull Request #13567 · php/php-src · GitHub

Best Regards,
Arnaud

This sounds like a bug with Mac vs. a PHP issue. That being said, the
biggest issue between changing these clocks is that ITIMER_PROF is
(practically, but not explicitly) monotonic. ITIMER_REAL can go
backwards (NTP clock adjustments) which might have interesting
side-effects if the clock is adjusted.

The problem might actually be using ITIMER_PROF, which "Measures CPU
time used by the process, including both user space and kernel space"
and usage of sockets/threads might give an "accelerated" value while
maybe ITIMER_VIRTUAL is the one we should be using since it "Measures
CPU time used by the process (user space)" which won't count kernel
timings.

Robert Landers
Software Engineer
Utrecht NL

On May 17, 2024, at 10:47 AM, Arnaud Le Blanc <arnaud.lb@gmail.com> wrote:

Hi internals,

There is an issue with max_execution_time on MacOS, probably only
MacOS 14 on Apple Silicon, that causes timeouts to fire too early [1].
max_execution_time is implemented with setitimer(ITIMER_PROF) on this
platform, and the SIGPROF signal is delivered too early for some
reason. Switching to ITIMER_REAL appears to fix the issue, and Manuel
Kress opened a PR [3].

Are there objections against switching to ITIMER_REAL on MacOS (Apple
Silicon only) in all currently supported PHP versions? (next 8.2.x,
8.3.x, 8.x)

Apart from fixing the issue, this would introduce the following minor
breaking changes:

- max_execution_time on MacOS would be based on wall-clock time rather
than CPU time, so the time spent in I/O, sleep(), and syscalls in
general would count towards the max_execution_time
- The SIGALRM signal would be used instead of the SIGPROF signal
(using SIGALRM conflicts with pcntl_alarm(), but SIGPROF conflicts
with profilers). As noted by Dmitry, it also conflicts with sleep() on
some platforms, however this should be safe on MacOS.

Currently max_execution_time is based on wall-clock time on ZTS and
Windows, and CPU time otherwise. On Linux and FreeBSD, wall-clock time
can also be used when building with
--enable-zend-max-execution-timers. Máté proposed to add a wall-clock
based timeout in the past [2] but the discussion has stalled. Any
thoughts about eventually switching other platforms to wall-clock
timeouts in the next 8.x ?

TL;DR:
- Any objection about using wall-clock max_execution_time and SIGALRM
on MacOS Apple Silicon in all supported versions?
- Thoughts about using wall-clock timeouts on all platforms in the next 8.x ?

[1] `max_execution_time` reached too early · Issue #12814 · php/php-src · GitHub
[2] Add support for limiting maximum execution time based on wall-time by kocsismate · Pull Request #6504 · php/php-src · GitHub
[3] Use `ITIMER_REAL` for timeout handling on Apple Silicon system by windaishi · Pull Request #13567 · php/php-src · GitHub

Best Regards,
Arnaud

FWIW, Cygwin and IBM i are also using wall time, because these platforms
don't do execution time. IIRC, the difference between platforms with how
max_execution_time is counted doesn't seem to be documented last I checked.

Hi Robert,

This sounds like a bug with Mac vs. a PHP issue.

This is what I believe as well. FWIW I was able to reproduce the issue
outside of PHP [2]. I've reported the bug to Apple, but I don't expect
it to be fixed quickly, if at all.

That being said, the
biggest issue between changing these clocks is that ITIMER_PROF is
(practically, but not explicitly) monotonic. ITIMER_REAL can go
backwards (NTP clock adjustments) which might have interesting
side-effects if the clock is adjusted.

Good point. Do you have references about ITIMER_REAL using a
non-monotonic clock, besides the lack of specification regarding the
clock? Based on experiments and the code [1], I think that ITIMER_REAL
uses a monotonic clock on MacOS. It's not ideal to rely on that, and
we should use a better specified timer mechanism if we eventually
switch to wall-clock time on all platforms, but it seems reasonable as
a workaround for the ITIMER_PROF bug on MacOS/Apple Silicon.

The problem might actually be using ITIMER_PROF, which "Measures CPU
time used by the process, including both user space and kernel space"
and usage of sockets/threads might give an "accelerated" value while
maybe ITIMER_VIRTUAL is the one we should be using since it "Measures
CPU time used by the process (user space)" which won't count kernel
timings.

Unfortunately ITIMER_VIRTUAL is not really useful as a
max_execution_time implementation as it will basically never fire in a
syscall-heavy workload. E.g. after replacing ITIMER_PROF by
ITIMER_VIRTUAL in [2], the program runs for well over the specified
time before receiving a signal, despite consuming a considerable
amount of resources.

[1] xnu/bsd/kern/kern_time.c at 94d3b452840153a99b38a3a9659680b2a006908e · apple-oss-distributions/xnu · GitHub
[2] README.md · GitHub

Best Regards,
Arnaud

Hi Calvin,

FWIW, Cygwin and IBM i are also using wall time, because these platforms
don't do execution time.

Thank you for the additional info.

IIRC, the difference between platforms with how
max_execution_time is counted doesn't seem to be documented last I checked.

Currently the max_execution_time documentation has a note about that,
but it only mentions Windows:

PHP: Runtime Configuration - Manual
On non Windows systems, the maximum execution time is not affected by system calls, stream operations etc. Please see the set_time_limit() function for more details.

Best Regards,
Arnaud

Hello,

I’m in favor of merging Arnaud’s patch for macOS while waiting for a better solution like relying on Grand Central Dispatch or another non-signal-based solution (https://github.com/php/php-src/pull/13468), which would allow max_execution_time to work with ZTS builds on mac as well.

I’m also in favor of using wall-clock time wherever possible (disclaimer: I’m the original author of this feature for Linux and FreeBSD).

Best,

On Tue, May 21, 2024, at 14:39, Arnaud Le Blanc wrote:

Hi Robert,

This sounds like a bug with Mac vs. a PHP issue.

This is what I believe as well. FWIW I was able to reproduce the issue

outside of PHP [2]. I’ve reported the bug to Apple, but I don’t expect

it to be fixed quickly, if at all.

That being said, the

biggest issue between changing these clocks is that ITIMER_PROF is

(practically, but not explicitly) monotonic. ITIMER_REAL can go

backwards (NTP clock adjustments) which might have interesting

side-effects if the clock is adjusted.

Good point. Do you have references about ITIMER_REAL using a

non-monotonic clock, besides the lack of specification regarding the

clock? Based on experiments and the code [1], I think that ITIMER_REAL

uses a monotonic clock on MacOS. It’s not ideal to rely on that, and

we should use a better specified timer mechanism if we eventually

switch to wall-clock time on all platforms, but it seems reasonable as

a workaround for the ITIMER_PROF bug on MacOS/Apple Silicon.

That was my only concern. That being said, I’m all in favor of wall-clock time, personally. It’s much easier to reason about.

The problem might actually be using ITIMER_PROF, which "Measures CPU

time used by the process, including both user space and kernel space"

and usage of sockets/threads might give an “accelerated” value while

maybe ITIMER_VIRTUAL is the one we should be using since it "Measures

CPU time used by the process (user space)" which won’t count kernel

timings.

Unfortunately ITIMER_VIRTUAL is not really useful as a

max_execution_time implementation as it will basically never fire in a

syscall-heavy workload. E.g. after replacing ITIMER_PROF by

ITIMER_VIRTUAL in [2], the program runs for well over the specified

time before receiving a signal, despite consuming a considerable

amount of resources.

[1] https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/kern_time.c#L432

[2] https://gist.github.com/arnaud-lb/012195a2fe4d3a2c1bff530a73ae6b11

Best Regards,

Arnaud

— Rob

On Fri, May 17, 2024 at 3:49 PM Arnaud Le Blanc <arnaud.lb@gmail.com> wrote:

Hi internals,

There is an issue with max_execution_time on MacOS, probably only
MacOS 14 on Apple Silicon, that causes timeouts to fire too early [1].
max_execution_time is implemented with setitimer(ITIMER_PROF) on this
platform, and the SIGPROF signal is delivered too early for some
reason. Switching to ITIMER_REAL appears to fix the issue, and Manuel
Kress opened a PR [3].

Are there objections against switching to ITIMER_REAL on MacOS (Apple
Silicon only) in all currently supported PHP versions? (next 8.2.x,
8.3.x, 8.x)

Apart from fixing the issue, this would introduce the following minor
breaking changes:

  • max_execution_time on MacOS would be based on wall-clock time rather
    than CPU time, so the time spent in I/O, sleep(), and syscalls in
    general would count towards the max_execution_time
  • The SIGALRM signal would be used instead of the SIGPROF signal
    (using SIGALRM conflicts with pcntl_alarm(), but SIGPROF conflicts
    with profilers). As noted by Dmitry, it also conflicts with sleep() on
    some platforms, however this should be safe on MacOS.

Currently max_execution_time is based on wall-clock time on ZTS and
Windows, and CPU time otherwise. On Linux and FreeBSD, wall-clock time
can also be used when building with
–enable-zend-max-execution-timers. Máté proposed to add a wall-clock
based timeout in the past [2] but the discussion has stalled. Any
thoughts about eventually switching other platforms to wall-clock
timeouts in the next 8.x ?

TL;DR:

  • Any objection about using wall-clock max_execution_time and SIGALRM
    on MacOS Apple Silicon in all supported versions?
  • Thoughts about using wall-clock timeouts on all platforms in the next 8.x ?

[1] https://github.com/php/php-src/issues/12814
[2] https://github.com/php/php-src/pull/6504
[3] https://github.com/php/php-src/pull/13567

Best Regards,
Arnaud

For reference: There is also this RFC for max_execution_wall_time that was discussed four years ago. https://externals.io/message/112492#112492

Hi

On 5/21/24 14:39, Arnaud Le Blanc wrote:

This is what I believe as well. FWIW I was able to reproduce the issue
outside of PHP [2]. I've reported the bug to Apple, but I don't expect
it to be fixed quickly, if at all.

Is the bug report public / can you provide a link to it?

Best regards
Tim Düsterhus

Hi Tim,

The report submitted to Apple via Feedback Assistant is not public,
but here is a mirror:
https://openradar.appspot.com/radar?id=5583058442911744

Best regards,
Arnaud

On Thu, May 23, 2024 at 8:26 PM Tim Düsterhus <tim@bastelstu.be> wrote:

Hi

On 5/21/24 14:39, Arnaud Le Blanc wrote:
> This is what I believe as well. FWIW I was able to reproduce the issue
> outside of PHP [2]. I've reported the bug to Apple, but I don't expect
> it to be fixed quickly, if at all.

Is the bug report public / can you provide a link to it?

Best regards
Tim Düsterhus