Sign inSign up

maindg/php-multi-process-failure

By maindg

Updated over 8 years ago

PHP script causes the docker container to freeze up. PHP script works fine running on host machine.

Image
0

260

maindg/php-multi-process-failure repository overview

How to run:

docker pull maindg/php-multi-process-failure

docker run -it maindg/php-multi-process-failure


Docker File

FROM microsoft/windowsservercore:1803

# install visual C++ runtime
RUN powershell -Command \
    Invoke-WebRequest -Uri "https://aka.ms/vs/15/release/VC_redist.x64.exe" -OutFile "/vc_redist.x64.exe"; \
    Start-Process /vc_redist.x64.exe -ArgumentList '/install', '/passive' -NoNewWindow -Wait; \
    Remove-Item -Force /vc_redist.x64.exe

# install php
RUN powershell -Command \
    # Add as fix for downloading from windows.php.net site due to certificate checks
    add-type 'using System.Net;using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; }  } '; \
    $AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'; \
    [System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols; \
    [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy; \
    # End add fix for downloading
    mkdir /install; \
    Invoke-WebRequest -Uri "https://windows.php.net/downloads/releases/php-7.2.6-nts-Win32-VC15-x64.zip" -OutFile "/install/php.zip"; \
    Expand-Archive /install/php.zip -DestinationPath /php; \
    Remove-Item /install -Recurse -Force; \
    [Environment]::SetEnvironmentVariable('Path', $env:Path + ';C:\php', [EnvironmentVariableTarget]::Machine)

COPY ./GoSlow.php /GoSlow.php
COPY ./TestMultiProcess.php /TestMultiProcess.php

ENTRYPOINT ["php", "/TestMultiProcess.php"]


GoSlow.php

<?php
declare(strict_types=1);

echo "Starting Sleeping!\n";
sleep($argc > 1 ? (int)$argv[1] : rand(5, 20));
echo "Done Sleeping\n";

TestMultiProcess.php

<?php
declare(strict_types=1);

$codeCheck = new TestMultiProcess();
$codeCheck->runTest();

class TestMultiProcess
{


    /**
     * Keeps track of running processes and starting times.
     * @var array
     */
    public static $processArray;

    public function runTest(): void
    {
        $wholeTime = microtime(true);
        $workingDir = __DIR__;

        $baseCommand = 'php GoSlow.php';
        $processCount = 20;
        try {
            for ($i = 0; $i < $processCount; $i++) {
                $time = time();
                $timeLength = rand(1, 8);
                echo "Starting {$baseCommand} $i with time: $timeLength\n";
                self::$processArray[$i]['startTime'] = $time;
                $process = $this->runProcess($baseCommand, [$timeLength], $workingDir);
                self::$processArray[$i]['process'] = $process;
            }

            // Wait for output from each process
            while (!empty(self::$processArray)) {
                foreach (self::$processArray as $i => $processData) {
                    /** @var Process $process */
                    $process = $processData['process'];
                    if (!$process->isRunning()) {
                        $process->waitForExit();
                        echo "\n======================\n";
                        $startTime = self::$processArray[$i]['startTime'];
                        if ($process->getStandardErrorResult() !== "") {
                            throw new RuntimeException($process->getStandardErrorResult());
                        }

                        echo "Process $i is Finished\n";
                        echo $process->getStandardOutResult();
                        $duration = microtime(true) - $startTime;
                        echo "\nTime: $duration seconds\n";
                        echo "\n\n";
                        unset(self::$processArray[$i]);
                    }
                }
                // Wait a quarter second before checking again.
                usleep(25000);
            }

        } catch (Throwable $e) {
            echo "\nFailure Starting Process due to Exception: [\n" . $e->getMessage() . "]\n";
            exit(2);
        }

        echo "\nTotal Time: " . (microtime(true) - $wholeTime) . "seconds.";
    }


    /**
     * @param string $process
     * @param array $arguments
     * @param string $workingDir
     * @return Process
     */
    private function runProcess(string $process, array $arguments, string $workingDir): Process
    {
        $process = new Process($process, $arguments);
        $process->setCwd($workingDir);
        $process->startForOutputCapture();

        return $process;
    }
}

class Process
{
    /**
     * @var string
     */
    private $command;
    /**
     * @var array
     */
    private $descriptorSpec;
    /**
     * @var resource[]
     */
    private $pipes;
    /**
     * @var resource
     */
    private $process;
    /**
     * @var string[]
     */
    private $outputValues;
    /**
     * @var int
     */
    private $returnCode;
    /**
     * @var string
     */
    private $cwd;

    /**
     * @param string $command
     * @param string[] $arguments
     */
    public function __construct($command, $arguments = [])
    {
        $this->command =
            $command . ' ' .
            implode(' ', $arguments);
        $this->descriptorSpec = [];
        $this->outputValues = [];
        $this->pipes = [];
    }

    public function startForOutputCapture()
    {
        $this->descriptorSpec = [
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w']
        ];
        $this->start();

        // Set up output capturing.
        foreach (array_keys($this->descriptorSpec) as $handle) {
            $this->outputValues[$handle] = '';
        }
    }

    /**
     * @return int
     */
    public function runAndCaptureOutput()
    {
        $this->startForOutputCapture();
        $this->waitForExit();
        return $this->returnCode;
    }

    /**
     * Begins the process.
     */
    public function start()
    {
        // Set up the argument list.
        $arguments = [
            $this->command,
            $this->descriptorSpec,
            &$this->pipes,
            $this->cwd,
            null,
            ['binary_pipes' => true, 'bypass_shell' => true]
        ];

        // Call proc_open with the array. (We do it this way so we only need to create $arguments once.)
        $this->process = call_user_func_array('proc_open', $arguments);
        // Send the arguments into an exception if it didn't work.
        if (!is_resource($this->process)) {
            throw new \RuntimeException("Failed to start process with arguments: " . print_r($arguments, true));
        }
    }

    /**
     * Waits for process termination and determines its return code.
     *
     */
    public function waitForExit()
    {
        if ($this->process) {
            $this->captureAnyOutput();
            $this->returnCode = proc_close($this->process);
            $this->process = null;
        }
    }

    public function terminate()
    {
        if ($this->process) {
            //proc_terminate does not work well on windows, so use taskkill
            if (strncasecmp(PHP_OS, 'WIN', 3) == 0) {
                $status = proc_get_status($this->process);
                if ($status['running']) {
                    $this->returnCode = exec('taskkill /F /T /PID ' . $status['pid']);
                }
            } else {
                $this->returnCode = proc_terminate($this->process);
            }
            $this->process = null;
        }
    }

    /**
     * @return boolean
     */
    public function isRunning()
    {
        $statusArray = proc_get_status($this->process);
        return $statusArray['running'];
    }

    /**
     * @return int
     */
    public function getReturnCode()
    {
        return $this->returnCode;
    }

    /**
     * @return string
     */
    public function getStandardOutResult()
    {
        return $this->getCaptureResult(1);
    }

    /**
     * @return string
     */
    public function getStandardErrorResult()
    {
        return $this->getCaptureResult(2);
    }

    /**
     * @return string
     */
    public function getCommand()
    {
        return $this->command;
    }

    /**
     * @return string
     */
    public function getCwd()
    {
        return $this->cwd;
    }

    /**
     * @param string $cwd
     */
    public function setCwd($cwd)
    {
        $this->cwd = $cwd;
    }

    /**
     * @param int $index
     * @return string
     */
    private function getCaptureResult($index)
    {
        return
            array_key_exists($index, $this->descriptorSpec) &&
            $this->descriptorSpec[$index] == ['pipe', 'w']
                ? $this->outputValues[$index]
                : null;
    }

    public function captureOutput()
    {
        foreach ($this->pipes as $handle => $pipe) {
            // Read some stuff.
            $result = stream_get_contents($pipe);
            $this->outputValues[$handle] .= $result;
        }
    }

    /**
     * Captures all output and saves it to local variables.
     *
     */
    public function captureAnyOutput()
    {
        while (count($this->pipes) > 0) {
            foreach ($this->pipes as $handle => $pipe) {
                // Read some stuff.
                $result = stream_get_contents($pipe);
                $this->outputValues[$handle] .= $result;

                // Mark and close the pipe if it's finished.
                if (feof($pipe)) {
                    fclose($pipe);
                    unset($this->pipes[$handle]);
                }
            }
        }
    }
}

Tag summary

Content type

Image

Digest

Size

1.8 GB

Last updated

over 8 years ago

docker pull maindg/php-multi-process-failure