amphp / parallel

An advanced parallelization library for PHP, enabling efficient multitasking, optimizing resource use, and application responsiveness through multiple CPU threads.
MIT License
775 stars 64 forks source link

Class not found #71

Closed cctjl closed 5 years ago

cctjl commented 5 years ago

I run codes like this:

use Amp\Parallel\Worker;
use Amp\Promise;
use common\services\internal\TigerApi;

        $work = Worker\enqueueCallable([TigerApi::class, 'getPoliciesTitle']);
        $promises = ['policy' => $work];
        $result = Promise\wait(Promise\all($promises));

The output: Uncaught Error in worker with message \"Class 'common\\services\\internal\\TigerApi' not found\" and code \"0\"

How can I add the class to autoload? I used Yii2.0 and I tried the method: https://github.com/amphp/parallel/issues/55, but it appeared yii is not found.

I know that yii has it's owner autoload class, how can I let this package use the autoloader of yii?

cctjl commented 5 years ago

Thanks all. I want to know why it cannot use the autoloader of yii. I created one composer package, and in the package, I can use yii without any problems. So why it does not work in this amphp/parallel?

I tried add the class "TigerApi" to composer.json, then it reminded me "yii is not found" because TigerApi used yii. I think it's because amphp/parallel used the standard composer autoloader. How can I let amphp/parallel use the autoloader which is registered by yii?

I think it's reasonable that composer package use the same autoloaders as the main process. If it is possible, many php frameworks will be easy to use.

trowski commented 5 years ago

This library runs PHP code enqueued to a Worker in a separate process or thread (context). That context must be initialized like any other PHP script. This generally means including an autoloader. The composer autoloader is included automatically – and generally works for projects using only composer for package management.

This question has now come up often enough that I think it would be a good idea to add support for specifying another autoloader file to be included when the context is started. https://github.com/amphp/parallel/commit/7c8756c3a59632c4480fa6b8c283b6c5430de770 in the ext-parallel branch implements this with AutoloadingWorkerFactory. You can set the global worker factory to use AutoloadingWorkerFactory using the factory() function in the Amp\Parallel\Worker namespace.

Worker\factory(new AutoloadingWorkerFactory('/path/to/custom/autoload.php'));

Call this function before using enqueue or enqueueCallable.

Please give the ext-parallel branch a try and let me know if AutoloadingWorkerFactory works for you.

cctjl commented 5 years ago

Thanks trowski. I tried and the same error found.

First git clone amp/parallel then git checkout to ext-parallel branch. I created a file CustomerApi.php and add the test function:

    use Yii;
    class CustomerApi
    {
        public static function testParallel()
        {
            $method = Yii::$app->request->getMethod();
            return $method;
        }
    }

Then run it in a yii controller:

    use Amp\Parallel\Worker;
    use Amp\Promise;
    use common\services\internal\CustomerApi;

        $promises = [];
        $autoloading_factory = new Worker\AutoloadingWorkerFactory(PROJECT_ROOT . '/vendor/yiisoft/yii2/Yii.php'); // this file is the same as yii used
        Worker\factory($autoloading_factory);
        $promises['address_settings'] = Worker\enqueueCallable([CustomerApi::class, 'testParallel']);
        $result = Promise\wait(Promise\all($promises));
        exit(var_dump($result));

Result is "Uncaught Error in worker with message \"Class 'common\services\internal\CustomerApi' not found\" and code \"0\"". I confirmed that the Worker\factory function received correct params. Yii version is 2.0.14.2.

trowski commented 5 years ago

Is CustomerApi defined in a way that it is autoloadable by either the composer autoloader or the Yii autoloader?

cctjl commented 5 years ago

Is CustomerApi defined in a way that it is autoloadable by either the composer autoloader or the Yii autoloader?

I think yii autoloader can find it. Because if I execute it directly in controller, it is correct. like this:

use common\services\internal\CustomerApi;

// CustomerApi.php is in common/services/internal/CustomerApi.php
CustomerApi::testParallel();
trowski commented 5 years ago

@cctjl Are you telling Yii to autoload classes from common\services\internal somewhere in your code. I'm guessing that's what's missing from the worker.

trowski commented 5 years ago

I modified the ext-parallel branch with this commit, renaming AutoloadingWorkerFactory to BootstrapWorkerFactory as I think this better represents what is being done here – including another file to setup the worker. @cctjl please run composer update and update your code accordingly.

I did some quick testing with Yii and was able to make the following work.

<?php // bootstrap.php

require __DIR__ . '/vendor/yiisoft/yii2/Yii.php';

Yii::setAlias('services', 'common/services');
<?php // test.php

require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/vendor/yiisoft/yii2/Yii.php';

use Amp\Parallel\Worker;
use Amp\Promise;
use services\internal\CustomerApi;

Yii::setAlias('services', 'common/services');

Worker\factory(new Worker\BootstrapWorkerFactory(__DIR__ . '/bootstrap.php'));

$result = Promise\wait(Worker\enqueueCallable([CustomerApi::class, 'testParallel']));

var_dump($result);
<?php // common/services/internal/CustomerApi.php

namespace services\internal;

class CustomerApi
{
    public static function testParallel()
    {
        return 'It worked!';
    }
}

Running test.php gives me the following:

string(10) "It worked!"

Define whatever aliases or other autoloading hooks you need in bootstrap.php and use this file with BootstrapWorkerFactory. Hopefully that will set up the worker so it can autoload classes from Yii.

cctjl commented 5 years ago

I modified the ext-parallel branch with this commit, renaming AutoloadingWorkerFactory to BootstrapWorkerFactory as I think this better represents what is being done here – including another file to setup the worker. @cctjl please run composer update and update your code accordingly.

I did some quick testing with Yii and was able to make the following work.

<?php // bootstrap.php

require __DIR__ . '/vendor/yiisoft/yii2/Yii.php';

Yii::setAlias('services', 'common/services');
<?php // test.php

require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/vendor/yiisoft/yii2/Yii.php';

use Amp\Parallel\Worker;
use Amp\Promise;
use services\internal\CustomerApi;

Yii::setAlias('services', 'common/services');

Worker\factory(new Worker\BootstrapWorkerFactory(__DIR__ . '/bootstrap.php'));

$result = Promise\wait(Worker\enqueueCallable([CustomerApi::class, 'testParallel']));

var_dump($result);
<?php // common/services/internal/CustomerApi.php

namespace services\internal;

class CustomerApi
{
    public static function testParallel()
    {
        return 'It worked!';
    }
}

Running test.php gives me the following:

string(10) "It worked!"

Define whatever aliases or other autoloading hooks you need in bootstrap.php and use this file with BootstrapWorkerFactory. Hopefully that will set up the worker so it can autoload classes from Yii.

Yes, You are right. It's because I called "setAlias" so we need a "bootstrap.php". Now we can use the function in yii, but the yii components is empty in parallel process so I cannot call "getMethod" on Yii::$app->request in "CustomerApi.php". It's enough for me. Thanks.

trowski commented 5 years ago

Since the worker is a new PHP environment, none of the request data will be available in the worker. Workers should be used for CPU-intensive or blocking tasks. You can pull the information you need from a request, then send only that information to a worker.

That being said, I'm not sure how much this component makes sense in a traditional PHP framework that runs in a web server SAPI. What were you hoping to accomplish?

cctjl commented 5 years ago

Since the worker is a new PHP environment, none of the request data will be available in the worker. Workers should be used for CPU-intensive or blocking tasks. You can pull the information you need from a request, then send only that information to a worker.

That being said, I'm not sure how much this component makes sense in a traditional PHP framework that runs in a web server SAPI. What were you hoping to accomplish?

Sorry for I did not reply earlier and thanks for your help. You are right. I just found the fact and write it. I don't have any special meaning.. Thanks again for your help.

ioncode commented 4 years ago

Hi, how can we pass context app from Yii2 console app to parallelMap function ? app\components\websocket\SocketServer ` public function eventLoop (){

    DebugMessager::log( "Starting app event loop from ".getcwd());

    //child process for workers pool

    $pool = new DefaultPool();

    $poolData = 'ConferenceChildProcess';

    $promises = parallelMap(\range(1, 2), function($i) use ($poolData){
        echo "Process #$i started in $poolData".PHP_EOL;
        //define('AMQP_DEBUG', true);
        $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
        $channel = $connection->channel();

        $channel->queue_declare($poolData, false, true, false, false);

        echo " [*] Waiting for messages. To exit press CTRL+C\n";

        $callback = function ($msg) {
            echo ' [x] Received ', $msg->body, "\n";
            sleep(2);
            echo " [x] Done\n";
            $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
        };
        $channel->basic_qos(null, 1, null);
        $channel->basic_consume($poolData, '', false, false, false, false, $callback);

        while ($channel->is_consuming()) {
            $channel->wait();
        }

        $channel->close();
        $connection->close();
        return 2;
    }, $pool);

    return Promise\wait($promises);`

causes Class 'app\components\websocket\SocketServer' not found in ..\src\vendor\opis\closure\src\SerializableClosure.php on line 263