Invoker.php 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of phpunit/php-invoker.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace SebastianBergmann\Invoker;
  11. use const SIGALRM;
  12. use function call_user_func_array;
  13. use function function_exists;
  14. use function pcntl_alarm;
  15. use function pcntl_async_signals;
  16. use function pcntl_signal;
  17. use function sprintf;
  18. use Throwable;
  19. final class Invoker
  20. {
  21. private int $timeout;
  22. /**
  23. * @throws Throwable
  24. */
  25. public function invoke(callable $callable, array $arguments, int $timeout): mixed
  26. {
  27. if (!$this->canInvokeWithTimeout()) {
  28. throw new ProcessControlExtensionNotLoadedException(
  29. 'The pcntl (process control) extension for PHP is required'
  30. );
  31. }
  32. pcntl_signal(
  33. SIGALRM,
  34. function (): void
  35. {
  36. throw new TimeoutException(
  37. sprintf(
  38. 'Execution aborted after %d second%s',
  39. $this->timeout,
  40. $this->timeout === 1 ? '' : 's'
  41. )
  42. );
  43. },
  44. true
  45. );
  46. $this->timeout = $timeout;
  47. pcntl_async_signals(true);
  48. pcntl_alarm($timeout);
  49. try {
  50. return call_user_func_array($callable, $arguments);
  51. } finally {
  52. pcntl_alarm(0);
  53. }
  54. }
  55. public function canInvokeWithTimeout(): bool
  56. {
  57. return function_exists('pcntl_signal') && function_exists('pcntl_async_signals') && function_exists('pcntl_alarm');
  58. }
  59. }