<commit_before><?php

namespace Aerys;

use Amp\{
    UvReactor,
    function coroutine,
    function resolve
};

abstract class Process {
    const STOPPED = 0;
    const STARTED = 1;
    const STOPPING = 2;

    private $logger;
    private $exitCode = 0;
    private $state = self::STOPPED;

    abstract protected function doStart(Console $console): \Generator;
    abstract protected function doStop(): \Generator;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }

    /**
     * Start the process
     *
     * @param \Aerys\Console $console
     * @return \Generator
     */
    public function start(Console $console): \Generator {
        try {
            if ($this->state) {
                throw new \LogicException(
                    "A process may only be started once"
                );
            }

            $this->registerSignalHandler();
            $this->registerShutdownHandler();
            $this->registerErrorHandler();

            $this->state = self::STARTED;

            yield from $this->doStart($console);

            // Once we make it this far we no longer want to terminate
            // the process in the event of an uncaught exception inside
            // the event reactor -- log it instead.
            \Amp\onError([$this->logger, "critical"]);
        } catch (\Throwable $uncaught) {
            $this->exitCode = 1;
            $this->logger->critical($uncaught);
            static::exit();
        }
    }

    /**
     * Stop the process
     *
     * @return \Generator
     */
    public function stop(): \Generator {
        try {
            switch ($this->state) {
                case self::STOPPED:
                case self::STOPPING:
                    return;
                case self::STARTED:
                    break;
            }
            $this->state = self::STOPPING;
            yield from $this->doStop();
        } catch (\Throwable $uncaught) {
            $this->exitCode = 1;
            $this->logger->critical($uncaught);
        } finally {
            static::exit();
        }
    }

    private function registerSignalHandler() {
        if (php_sapi_name() === "phpdbg") {
            // phpdbg captures SIGINT so don't bother inside the debugger
            return;
        }

        $onSignal = coroutine([$this, "stop"]);

        if (\Amp\reactor() instanceof UvReactor) {
            \Amp\onSignal(\UV::SIGINT, $onSignal);
            \Amp\onSignal(\UV::SIGTERM, $onSignal);
        } elseif (extension_loaded("pcntl")) {
            \Amp\repeat("pcntl_signal_dispatch", 1000);
            pcntl_signal(\SIGINT, $onSignal);
            pcntl_signal(\SIGTERM, $onSignal);
        }
    }

    private function registerShutdownHandler() {
        register_shutdown_function(function() {
            if (!$err = \error_get_last()) {
                return;
            }

            switch ($err["type"]) {
                case E_ERROR:
                case E_PARSE:
                case E_USER_ERROR:
                case E_CORE_ERROR:<commit_msg>Use keep_alive to allow proper termination on script end<commit_after><?php

namespace Aerys;

use Amp\{
    UvReactor,
    function coroutine,
    function resolve
};

abstract class Process {
    const STOPPED = 0;
    const STARTED = 1;
    const STOPPING = 2;

    private $logger;
    private $exitCode = 0;
    private $state = self::STOPPED;

    abstract protected function doStart(Console $console): \Generator;
    abstract protected function doStop(): \Generator;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }

    /**
     * Start the process
     *
     * @param \Aerys\Console $console
     * @return \Generator
     */
    public function start(Console $console): \Generator {
        try {
            if ($this->state) {
                throw new \LogicException(
                    "A process may only be started once"
                );
            }

            $this->registerSignalHandler();
            $this->registerShutdownHandler();
            $this->registerErrorHandler();

            $this->state = self::STARTED;

            yield from $this->doStart($console);

            // Once we make it this far we no longer want to terminate
            // the process in the event of an uncaught exception inside
            // the event reactor -- log it instead.
            \Amp\onError([$this->logger, "critical"]);
        } catch (\Throwable $uncaught) {
            $this->exitCode = 1;
            $this->logger->critical($uncaught);
            static::exit();
        }
    }

    /**
     * Stop the process
     *
     * @return \Generator
     */
    public function stop(): \Generator {
        try {
            switch ($this->state) {
                case self::STOPPED:
                case self::STOPPING:
                    return;
                case self::STARTED:
                    break;
            }
            $this->state = self::STOPPING;
            yield from $this->doStop();
        } catch (\Throwable $uncaught) {
            $this->exitCode = 1;
            $this->logger->critical($uncaught);
        } finally {
            static::exit();
        }
    }

    private function registerSignalHandler() {
        if (php_sapi_name() === "phpdbg") {
            // phpdbg captures SIGINT so don't bother inside the debugger
            return;
        }

        $onSignal = coroutine([$this, "stop"]);

        if (\Amp\reactor() instanceof UvReactor) {
            \Amp\onSignal(\UV::SIGINT, $onSignal, ["keep_alive" => false]);
            \Amp\onSignal(\UV::SIGTERM, $onSignal, ["keep_alive" => false]);
        } elseif (extension_loaded("pcntl")) {
            \Amp\repeat("pcntl_signal_dispatch", 1000);
            pcntl_signal(\SIGINT, $onSignal);
            pcntl_signal(\SIGTERM, $onSignal);
        }
    }

    private function registerShutdownHandler() {
        register_shutdown_function(function() {
            if (!$err = \error_get_last()) {
                return;
            }

            switch ($err["type"]) {
                case E_ERROR:
                case E_PARSE:
                case E_USER_ERROR:
                case E_CORE_ERROR:
{'fraction_non_alphanumeric': 0.11680229775011967, 'fraction_numerical': 0.0031913196106590075, 'mean_word_length': 1.6193063100710405, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3573328', 'n_tokens_mistral': 1771, 'n_tokens_neox': 1673, 'n_words': 419}