Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Process] Add a Process Manager #8753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from

Conversation

romainneutron
Copy link
Contributor

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes, require #8756 to be merged
Fixed tickets #8454
License MIT
Doc PR Not ready yet

Hello,

I'm looking for feedback for this process manager implementation.

This PR adds a ProcessableInterface that is implements by both Process and ProcessManager.

Once the API is ok, the remaining tasks are :

  • Write documentation.
  • Add more tests.

Usage examples

Run 4 jobs in parallel

$manager = new Symfony\Component\Process\Manager\ProcessManager();
$manager
    ->add(new Process('...'))
    ->add(new Process('...'))
    ->add(new Process('...'))
    ->add(new Process('...'))
    ->run();

Run 4 jobs in queue

$manager = new Symfony\Component\Process\Manager\ProcessManager();
$manager
    ->setMaxParallelProcesses(1)
    ->add(new Process('...'))
    ->add(new Process('...'))
    ->add(new Process('...'))
    ->add(new Process('...'))
    ->run();

Run 2 queues of 2 jobs in parallel

$queue1 = new Symfony\Component\Process\Manager\ProcessManager();
$queue1
    ->setMaxParallelProcesses(1)
    ->add(new Process('...'))
    ->add(new Process('...'));

$queue2 = new Symfony\Component\Process\Manager\ProcessManager();
$queue2
    ->setMaxParallelProcesses(1)
    ->add(new Process('...'))
    ->add(new Process('...'));

$manager = new Symfony\Component\Process\Manager\ProcessManager();
$manager
    ->add($queue1)
    ->add($queue2)
    ->run();

*
* @return Process The new process
*
* @throws \RuntimeException When process can't be launch or is stopped
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phpdocs in this interface mix up \RuntimeException and RuntimeException

@sirian
Copy link
Contributor

sirian commented Aug 29, 2013

Is there any way to add processes to queue on the fly?

for example I have 10000 users in DB. and for each user I want to call some process (but only 5 in parallel). So it's silly to get all 10000 users and create 10000 processes at once.

Maybe add some process providers, or add events

@Baachi
Copy link
Contributor

Baachi commented Aug 29, 2013

Cool feature 👍


namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused

@romainneutron
Copy link
Contributor Author

What about a process manager builder ? Something like this :

namespace Symfony\Component\Process\Manager;

class ManagerBuilder
{
    public function chain(array $processes)
    {
        $manager = new ProcessManager();
        $manager->setMaxParallelProcesses(1);

        foreach ($processes as $name => $process) {
            $manager->add($process, $name);
        }

        return $manager;
    }

    public function parallel(array $processes)
    {
        $manager = new ProcessManager();

        foreach ($processes as $name => $process) {
            $manager->add($process, $name);
        }

        return $manager;
    }
}

usage example :

$manager = $builder->chain(
    $builder->parallel(
        $builder->chain(array($process1, $process2, $process3)),
        $builder->chain(array($process4, $process5))
    ),
    $process6
);
$manager->run();

any ideas ?

@sirian
Copy link
Contributor

sirian commented Sep 6, 2013

@romainneutron Maybe also smth like auto method from node.js async library?
example from library

async.auto({
    get_data: function(callback){
        // async code to get some data
    },
    make_folder: function(callback){
        // async code to create a directory to store a file in
        // this is run at the same time as getting the data
    },
    write_file: ['get_data', 'make_folder', function(callback){
        // once there is some data and the directory exists,
        // write the data to a file in the directory
        callback(null, filename);
    }],
    email_link: ['write_file', function(callback, results){
        // once the file is written let's email a link to it...
        // results.write_file contains the filename returned by write_file.
    }]
});

@romainneutron
Copy link
Contributor Author

Hello,

node.js is an event driven environment, and async.js is a tool to deal with async behaviors. Actually, Process is not asynchronous. The Process::start method allows you to run a process in "background" but the polling mechanism should be triggered periodically to fetch output (and results). This polling mechanism is triggered whenever you call a status method (Process::isRunning, Process::checkTimeout, Process::getOutput, etc).

So this kind of behavior could not be ported. Anyway, not in the same fashion. If you're interested in async process, have a look at react project, particularly the child process PR and the https://github.com/reactphp/promise project that provides promise for the react environment. This is out of topic because this environment requires non-blocking calls, what's not at all the case of Symfony.

@sirian
Copy link
Contributor

sirian commented Sep 6, 2013

@romainneutron I mean ability to call processes in some order. But looks like I can achieve this using chain and parallel methods:

for example

$builder->chain(
    $builder->parallel($getDataProcess, $makeFolderProcess)),
    $writeFileProcess,
    $emailLinkProcess
);

@romainneutron
Copy link
Contributor Author

Yes it works. The difference is the ability to pass the output between process. This is something that we could implement in the future, see FR #8161

@romainneutron
Copy link
Contributor Author

I mean that $writeFileProcess could use the output of $getDataProcess

@romainneutron
Copy link
Contributor Author

@fabpot what's the state on this one ? Could it be merge in 2.4 ? If so, I've to finish the ManagerBuilder and work on the doc ASAP.

If it's too late for this, can we merge the interfaces (2 first commits) so the ProcessManager can be used and tested without using a custom symfony repo ?

@fabpot
Copy link
Member

fabpot commented Sep 7, 2013

We are not in a hurry. 2.4 end of dev is at the end of September. I haven't had time to have a look at this PR yet, but I will soon.

@ubick
Copy link

ubick commented Sep 13, 2013

👍 Great feature! Would love to see this integrated into 2.4.

@tPl0ch
Copy link

tPl0ch commented Sep 22, 2013

I'd really like to see something like a ProcessProvider:

<?php
class ManagerBuilder
{
    public function chain(ProcessProviderInterface $processProvider)
    {
        $manager = ProcessManager::create();
        $manager->setMaxParallelProcesses(1);

        foreach ($processProvider as $name => $process) {
            $manager->add($process, $name);
        }

        return $manager;
    }

    public function parallel(ProcessProviderInterface $processProvider)
    {
        $manager = ProcessManager::create();

        foreach ($processProvider as $name => $process) {
            $manager->add($process, $name);
        }

        return $manager;
    }
}

Usage example:

<?php

// Implemets ProcessProviderInterface
$batchProcessProvider = new BatchProcessProvider();
$batchProcessProvider
    ->setCommandTemplate(
        'exec php -f app/console project:update-users --limit=%limit% --offset=%offset%'
    )
    ->setPlaceholders(array(
        '%limit%'   => 'limit',
        '%offset%' => 'offset'
    ))
    ->setTotalCount(1000000)
    ->setLimit(20000)
;

$q = $em->createQuery('select u from MyProject\Model\User u');

$doctrineBatchProcessProvider = BatchProcessProvider::createFromIterator(
    $q->iterate(),
    'exec php -f app/console project:update-user-attributes --id=%id%',
    array('%id%' => 'id')
);


$manager = $builder->chain(
    $builder->parallel(
        $builder->chain($batchProcessProvider),
        $builder->chain(ProcessProvider::createFromArray(
            array(
                $process4,
                'processName' => $process5
            )
        )),
        $doctrineBatchProcessProvider
    ),
    ProcessProvider::createFromArray(array($process6))
);
$manager->run();

/cc @sirian

@sirian
Copy link
Contributor

sirian commented Sep 22, 2013

@tPl0ch imho - it's silly to add all processes at start point like in ManagerBuilder::parallel example (to decrease memory usage).
My sugesstion - ProcessProvider should implement Iterator interface (http://ru2.php.net/manual/ru/class.iterator.php), and ProcessManager should work correctly with this providers (when free slot for process is available - get next process from process provider)

@romainneutron
Copy link
Contributor Author

ProcessProvider is indeed something that would be quite nice. But we need to separate concerns and ProcessProvider should not deal directly with ProcessManager that has the job to run multiple instancied processes.

I think it should be implemented at higher level than ProcessManager. To do so, we need to some informations about its state like ProcessManager::hasSlot or ProcessManager::getQueueLength. What do you think about this ?

@romainneutron
Copy link
Contributor Author

ping @fabpot

@romainneutron
Copy link
Contributor Author

@fabpot If this PR is not mergeable in 2.4, can we at least think about merging the interfaces (two first commits) ?

@fabpot
Copy link
Member

fabpot commented Oct 1, 2013

I will need more time on this one as I have something equivalent (but not exactly). That's something we will be able to do in 2.5.

@romainneutron
Copy link
Contributor Author

Okay then, bring the code at symfonycon hackday ;)

fabpot added a commit that referenced this pull request Oct 9, 2013
…mainneutron)

This PR was merged into the 2.2 branch.

Discussion
----------

[Process][2.2] Fix 9182 : random failure on pipes tests

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #9182
| License       | MIT

I'm not a big fan of this fix, but - at least - it works.

With this code, finally, Process does not behave the same at all on Windows and Linux.
This patch does not smells very good but it solves the random failing test issue (that produced at runtime too).

Actually, calling `proc_get_status` within the waiting loop introduced the bug.
So this PR reverts to the previous behavior (consider a process running as long as pipes give data). On Windows, this is not the same behavior as we're not using streams but file handles. Whereas the feof of a stream is detected when the other side closes, the feof of a file handle can be reached at any time. So, on Windows, `proc_get_status` is called (checking the feof of the file handle might be positive until the executable outputs something), and we consider a process running as long as the information returned says it's running.

We could think of decouple windows and linux logic in two separated objects, using the interfaces I introduced in #8753. This could bring much more readability and make the code more easy to understand.

Commits
-------

64a0b40 [Process] Fix #9182 : random failure on pipes tests
*/
private function addFailureToProcess($process, $e, $strategy)
{
$process->addFailure($e);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause a memory leak if the process is being retried using STRATEGY_RETRY.
The "failures" array will get bigger and bigger with each process retry.

Example below:

$manager = new ProcessManager();
$manager->setTimeoutStrategy(ProcessManager::STRATEGY_RETRY);

for ($i = 0; $i < 10; $i++) {
    $p = new Process("while true; do date > /dev/null; done;");
    $p->setTimeout(1);
    $manager->add($p);
}

$manager->start();

while ($manager->isRunning()) {
    echo sprintf("Memory usage: %dKB/ %dKB\n", round(memory_get_usage(true) / 1024), memory_get_peak_usage(true) / 1024);
}

@fabpot fabpot closed this Mar 26, 2014
@viviengaetan
Copy link
Contributor

ping @fabpot @romainneutron
Have you move forward on this manager? That's something really interesting and 2.5 is out for long (#8753 (comment))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.