amphp/beanstalk

Yield expression can only be used inside a function

anotherdevs opened this issue · 9 comments

Hi

I get this error: "PHP Fatal error: The "yield" expression can only be used inside a function" when I run this code:

// Autoload classes
require_once realpath(__DIR__) . '/vendor/autoload.php';

$beanstalk = new Amp\Beanstalk\BeanstalkClient("tcp://127.0.0.1:11300");

// This step not required if you included a tube query parameter when creating the client
$beanstalk->use('testtube');

while([$jobId, $jobData] = yield $beanstalk->reserve()) {
   print_r($jobId);
}

Steps to reproduce:

  1. Run: "composer require amphp/beanstalk"
  2. Create script with code from above
  3. Run: "php -f client.php"

Which yields the aforementioned error.

Setup:
Debian Jessie

php -v
PHP 7.2.3

uname -a
4.9.0-6-amd64

What is the problem here?

Bilge commented

yield doesn't just have to be used inside a function, it has to be used inside an async context (created with Loop::run()) when awaiting a Promise. At the very minimum you would have to wrap your loop in an async context (but you might want to put everything inside).

Loop::run(function () use ($beanstalk) {
    while ([$jobId, $jobData] = yield $beanstalk->reserve()) {
        print_r($jobId);
    }
}

Hi @Bilge

That would be:

// Autoload classes
require_once realpath(__DIR__) . '/vendor/autoload.php';

$beanstalk = new Amp\Beanstalk\BeanstalkClient("tcp://127.0.0.1:11300");

// This step not required if you included a tube query parameter when creating the client
$beanstalk->use('testtube');

Amp\Loop::run(function () use ($beanstalk)
{
   while ([$jobId, $jobData] = yield $beanstalk->reserve())
   {
      echo "Handle job: " . $jobId . "\n";
   }
});

No errors now, however no jobs are received. I have 16568 jobs in queue ready to be processed.

By the way, I got the previous code from here: https://amphp.org/beanstalk/jobs So that should probably needs to be updated some time. :-)

Do you have a hint why no messages are not getting handled?

@anotherdevs You call $beanstalk->use('testtube');, is that really the queue where your jobs are?

If you want to write asynchronous applications, you should familiarize yourself with Amp, which is the underlying framework that enables all our libraries.

Hi @kelunik

Yes, that is the tube I am using.

// Autoload classes
require_once realpath(__DIR__) . '/vendor/autoload.php';

use Pheanstalk\Pheanstalk;

$pheanstalk = new Pheanstalk('127.0.0.1');
$pheanstalk->watch('testtube');

if ($pheanstalk->getConnection()->isServiceListening() == true)
{
   while($job = $pheanstalk->reserve())
   {
      echo "Handle job: " . $job->getId() . "\n";
   }
}

This code works fine and reserves messages. As you can see I use the same tube name: 'testtube'.

I don't want to be rude, however I am not going to read a ton of documentation for a library that has proven to be very hard to get up and running.

You have documented some easy-to-go code, however it's not working as intended: https://amphp.org/beanstalk/jobs Are you able to use the provided code from the documentation and get it to reserve messages?

The code on that page assumes you have some knowledge of Amp, because that's what you need to write an asynchronous application with it. If you don't need async, I'd recommend you to use https://github.com/pda/pheanstalk instead. You can also continue to use this library and use Amp\Promise\wait() instead of yield if you don't need async.

Hi @kelunik

I do need async functionality, so this library is still very much in play. Are you able to provide me with a simple example code that does the same as the example I have posted using pheanstalk?

@anotherdevs The documentation was slightly wrong there, you need to use watch() instead of use() for consumers.

Hi @kelunik

And it works now. :-)

I used this code:

// Autoload classes
require_once realpath(__DIR__) . '/vendor/autoload.php';

$beanstalk = new Amp\Beanstalk\BeanstalkClient("tcp://127.0.0.1:11300");

// This step not required if you included a tube query parameter when creating the client
$beanstalk->use('testtube');

Amp\Loop::run(function () use ($beanstalk)
{
   while ([$jobId, $jobData] = yield $beanstalk->reserve())
   {
      echo "Handle job: " . $jobId . "\n";
   }
});

I don't find it faster than pheanstalk. Do I need more than watch() to make it completely async?

There is of course some network latency of about 10 ms.

amphp beanstalk: Elapsed time: 40899.302005768
pheanstalk: Elapsed time: 40835.518836975

Elapsed time is in seconds.

Batch was: 2000 jobs.

And of course the code here:

$start = microtime(true);

// Autoload classes
require_once realpath(__DIR__) . '/vendor/autoload.php';

$beanstalk = new Amp\Beanstalk\BeanstalkClient("tcp://127.0.0.1:11300");

$beanstalk->watch('testtube');

Amp\Loop::run(function () use ($start, $beanstalk)
{
   while ([$jobId, $jobData] = yield $beanstalk->reserve())
   {
         echo "Handle job: " . $jobId . "\n";

         yield $beanstalk->delete($jobId);

         $time_elapsed = (microtime(true) - $start) * 1000;
         echo "Elapsed time: " . $time_elapsed . "\n";
   }
});

Async won't make your code magically faster. It only allows doing other things while waiting for I/O. If your jobs are lightweight or can be processed with a non-blocking library, you can just create multiple consumers in one script, e.g.

$start = microtime(true);

require_once __DIR__ . '/vendor/autoload.php';

$beanstalk = new Amp\Beanstalk\BeanstalkClient("tcp://127.0.0.1:11300");
$beanstalk->watch('testtube');

Amp\Loop::run(function () use ($start, $beanstalk) {
    $consumer = function () use ($beanstalk) {
        while ([$jobId, $jobData] = yield $beanstalk->reserve()) {
            echo "Handle job: {$jobId}\n";
            yield $beanstalk->delete($jobId);
        }
    };

    yield [
        Amp\call($consumer),
        Amp\call($consumer),
    ];
});

But if you really want to benefit from this concurrency, you should really read the documentation for Amp.