muffin
muffin

Reputation: 2104

Yii Php Executing asynchronous background request

Hi i'm trying to execute a LONG RUNNING request (action) in background.

function actionRequest($id){

//execute very long process here in background but continue redirect

Yii::app()->user->setFlash('success', "Currently processing your request you may check it from time to time.");
$this->redirect(array('index', 'id'=>$id));
}

What i'm trying to achieve is to NOT have the user waiting for the request to be processed since it generally takes 5-10min, and the request usually goes to a timeout, and even if I set the timeout longer, waiting for 5-10 min. isn't a good user experience.

So I want to return to the page immediately notifying the user that his/her request is being processed, while he can still browse, and do other stuff in the application, he/she can then go back to the page and see that his/her request was processed.

I've looked into Yii extensions backjob, It works, the redirect is executed immediately (somehow a background request), but when doing other things, like navigating in the site, it doesn't load, and it seems that the request is still there, and i cannot continue using the application until the request is finished.

A similar extension runactions promises the same thing, but I could not even get it to work, it says it 'touches a url', like a fire and forget job but doesn't work.

I've also tried to look into message queuing services like Gearman, RabbitMQ, but is really highly technical, I couldn't even install Gearman in my windows machine so "farming" services won't work for me. Some answers to background processing includes CRON and AJAX but that doesn't sound too good, plus a lot of issues.

Is there any other workaround to having asynchronous background processing? I've really sought hard for this, and i'm really not looking for advanced/sophisticated solutions like "farming out work to several machines" and the likes. Thank You very much!

Upvotes: 4

Views: 3197

Answers (1)

Jerome
Jerome

Reputation: 291

If you want to be able to run asynchronous jobs via Yii, you may not have a choice but to dabble with some AJAX in order to retrieve the status of the job asynchronously. Here are high-level guidelines that worked for me. Hopefully this will assist you in some way!


Setting up a console action

To run background jobs, you will need to use Yii's console component. Under /protected/commands, create a copy of your web controller that has your actionRequest() (e.g. /protected/commands/BulkCommand.php).

This should allow you to go in your /protected folder and run yiic bulk request.

Keep in mind that if you have not created a console application before, you will need to set up its configuration similar to how you've done it for the web application. A straight copy of /protected/config/main.php into /protected/config/console.php should do 90% of the job.


Customizing an extension for running asynchronous console jobs

What has worked for me is using a combination of two extensions: CConsole and TConsoleRunner. TConsoleRunner uses popen to run shell scripts, which worked for me on Windows and Ubuntu. I simply merged its run() code into CConsole as follows:

public function popen($shell, $redirectOutput = '')
{
    $shell = $this->resolveCommandLine($shell, false, $redirectOutput);
    $ret = self::RETURN_CODE_SUCCESS;
    if (!$this->displayCommands) {
        ob_start();
    }

    if ($this->isWindows()) {
        pclose(popen('start /b '.$shell, 'r'));
    }
    else {
        pclose(popen($shell.' > /dev/null &', 'r'));
    }

    if (!$this->displayCommands) {
        ob_end_clean();
    }
    return $ret;
}

protected function isWindows()
{
    if(PHP_OS == 'WINNT' || PHP_OS == 'WIN32')
        return true;
    else 
        return false;
}

Afterwards, I changed CConsole's runCommand() to the following:

public function runCommand($command, $args, $async = false, &$outputLines = null, $executor = 'popen')
    {
        ...
        switch ($executor) {
            ...
            case 'popen':
                return $this->popen($shell);
            ...
        }
    }


Running the asynchronous job

With the above set up, you can now use the following snippet of code to call yiic bulk request we created earlier.

$console = new CConsole();
$console->runCommand('bulk request', array(
    '--arg1="argument"',
    '--arg2="argument"',
    '--arg3="argument"',
));

You would insert this in your original actionRequest().


Checking up on the status

Unfortunately, I'm not sure what kind of work your bulk request is doing. For myself, I was gathering a whole bunch of files and putting them in a folder. I knew going in how many files I expected, so I could easily create a controller action that verifies how many files have been created so far and give a % of the status as a simple division.

Upvotes: 1

Related Questions