How to reload code changes in CodeIgniter with RabbitMQ? - php

I am using CodeIgniter framework with RabbitMQ for doing time consuming activities such as sending emails, generating PDFs.
Whenever I change a code in the PHP files, say library or controller, until I restart the Apache, the old code is being executed by the RabbitMQ. How to resolve this?
The function in Controller,
public function receive()
{
$this->db->reconnect();
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('generate_pdf', false, false, false, false);
echo "[*] Started listening to connections... To exit press CTRL+C\n";
$generate_pdf = function ($data)
{
$this->generatepdf->generate($data); // Generatepdf.php library -> generate() function
};
$channel->basic_qos(null, 1, null);
$channel->basic_consume('generate_pdf', '', false, true, false, false, $generate_pdf)
while ($channel->is_consuming())
{
$channel->wait();
}
}
Starting this in termimal,
php index.php Controllerfile.php receive
This works fine as expected. So when a job is pushed to the queue, the generate() function in the library file Generatepdf.php is called and the execution is done.
However, if I modify the generate() function thru an editor or I re-upload the file with changes, the changes does not reflect until I restart the Apache server.
Function definition before changes:
public function generate()
{
echo "Hello the data is 250";
}
Output: Hello the data is 250
Function definition after the change:
public function generate()
{
echo "Hello the data is 251";
}
Output before restarting Apache: Hello the data is 250
Output after restarting Apache: Hello the data is 251
Why does this happen? How can I made the execution to refresh the changes in the file?

The code you've shown contains this loop:
while ($channel->is_consuming())
{
$channel->wait();
}
What that's saying is "unless the connection to RabbitMQ breaks, wait for a new message, process it, and then wait again, forever". In other words, it's an infinite loop, and the PHP process running it won't exit unless you kill it.
Contrary to some descriptions I've seen, PHP is a compiled language, not an interpreted one: when a PHP process is asked to load a file of PHP code, it looks at it once, compiles it to a machine-friendly representation (called "op codes") and then doesn't need to look at it again to execute the code in it any number of times. So when you edit the file, a process that's already running won't notice, and won't compile your new code.
If you want to have the new code loaded, you need to exit the infinite loop, so the process ends, and then start a new one, which will compile your new code. For instance, you can have a check in the loop for a particular file, measure the time it's been running, or listen for a special message telling it to exit. You can then use something like "supervisord" to ensure the script is re-run whenever it exits, or just have a "cron" task to regularly start a new copy.
As one extra point: there is a feature of PHP called "OpCache" which reuses the compiled version of files even between processes, for better performance. By default, it will look at the timestamp of the file to decide whether to recompile it, so will "notice" if you edit a file, but that check can be turned off, for maximum performance. If that was the problem, you'd see this with all PHP code, though, not just your RabbitMQ loop.

Related

Continue PHP Operation After User Exits Early

I'm using Laravel, PHP7, PHP-FPM, APCu and NGINX.
I have an HTML form where a user can upload a file, it connects to Upload.php.
File Process:
validate
name
move from /tmp to /media
create thumbnail
create database record
Once the PHP script reaches a certain point, how can I have it continue running even if the user exits the upload page early? Or else a rogue file will be left in the directory without a database entry.
// Move uploaded file from /tmp to /media
Input::file('file')->move("/var/www/mysite/media", $image);
// Continue even if user exits early
// prevent a file in /media from not having a database record
// Thumbnail creation and other operations here
// May take several seconds
// Save database record
$image = new Gallery();
$image->name = $name;
$image->created_at = $date;
$image->save();
Should I use ignore_user_abort(true) and wrap the operations in a while(true)?
I have other bools in the script such as $upload = true. How does the while(true) know to represent ignore_user_about(true) and not another bool I have set?
User abort is not an easy thing to catch in PHP... Typically, your script will run until it actually detects the client has baled out by trying to send something back to the browser... In your case, since you are not sending anything back while processing, you should run to completion... even if the user closes the connection... To make sure, you can use the register_shutdown_function() which will be called when PHP shuts down... now be careful, as Laravel has hooks there too... so any timeout will trigger Laravel error first, yours second... However, you can know in that function if it shut down properly or if it aborted...
To play with the function I created a route like this:
use Illuminate\Support\Facades\Log;
Route::get('/abort', function(){
Log::info('Entering abort route...');
set_time_limit(5);
register_shutdown_function(function(){
Log::info('Entering shutdown function... status: ' . connection_status());
switch(connection_status()){
case CONNECTION_ABORTED:
Log::info('Connection Aborted');
break;
case CONNECTION_TIMEOUT:
echo 'Connection Timeout';
break;
default:
echo 'All ok, user did not abort and function did not time out.';
}
});
while(1){
echo 'Ping<br />';
};
});
Here you can catch (I used the Laravel logger for the abort... find the log in storage/logs/laravel.log) ...
Now interestingly, if you abort this, you will get an abord call in the shutdown function, because the while(1) echoes 'Ping' to the browser, detecting the connection loss before the time out... however, if you remove the echo in the while and replace it with non buffer work $cnt++; or something, Then even if you abort, you will only get the timeout... the script did not detect the connection closing...
Note that your handler runs after all other handlers...
Also, it runs every time the script shuts down... so even when all is good... as in the default: switch above... naturally, this script will not run to completion because of the while(1)... just remove it if you want to see the normal completion behaviour...
I think this is probably the easiest way to do the clean up... catch it in there and do any clean up you need to...
Hope this helps...

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!
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.

Trigger function just before exit

I'm using DHTMLX Scheduler on the front end and DHTMLX Connector on the backend as part of my radio automation app. Every time a user edits the calendar, an AJAX call is made to a file that looks like this:
require_once("dhtmlxScheduler_v4/connector/scheduler_connector.php");
require_once('QDRAconf.php');
$res = mysql_connect($QDRAconf['mysqlHost'], $QDRAconf['mysqlUser'], $QDRAconf['mysqlPass']);
mysql_select_db($QDRAconf['mysqlDb']);
// init the schedulerconnector
$conn = new SchedulerConnector($res);
// render the table
$conn->render_table("events","id","start_date,end_date,text");
This file is my "shim" that hooks up the fronted to the back end. I want to run another PHP script that writes the changes to my crontab, but it needs to happen after the DHTMLX library has updated the database. Trouble is, the DHTMLX library will automatically exit whenever it thinks it's done: sometimes it might not get past the first require_once('...') line so I can't just put require_once('cronwriter.php'); at the last line of the script.
My solution to this was to create a class with a destructor that updates the crontab with the latest changes. Since the php manual states that destructors will still be run if the exit() or die() function is called, I added a dummy class with a destructor that runs cronwriter.php script: (I added this to the beginning of the file.)
class ExitCatcher
{
function __destruct()
{
require_once('cronwriter.php');
}
}
//init the class
$ExitCatcher = new ExitCatcher;
For some reason, it doesn't work.
register_shutdown_function may offer a quick solution; but, you might save yourself some future trouble by inspecting the cause of that library's sporadic process haltings.
A good place to start might be...
your browser's JS console for JS errors
your JS console's network tab for AJAX errors
your server's error logs for PHP errors

Can 32-Bit PHP run a .vbs script on a 64-Bit IIS Server?

There is a vbscript that we must run to consolidate information gathered in a custom web application into our management software. The .vbs is in the same folder as the web application which is built in CodeIgniter 2.
Here is the controller code:
public function saveToPM( $budgetType ){
// run it
$obj = new COM( 'WScript.Shell' );
if ( is_object ( $obj ) ) {
$obj->Run( 'cmd /C wscript.exe D:\pamtest\myload.vbs', 0, true );
var_dump($obj->Run);
} else {
echo 'can not create wshell object';
} // end if
$obj = null;
//$this->load->view('goodPush');
} // end saveToPM function
We have enabled DCon in the php.ini file and used dcomcnfg to enable permissions for the user.
I borrowed the code from http://www.sitepoint.com/forums/showthread.php?505709-run-a-vbs-from-php.
The screen echos "Code executed" but the vbscript does not run.
We have been fighting with this for a while so any help is GREATLY appreciated.
It's a bit messy. PHP calls WScript.Shell.Run which will call cmd (with /c - i.e terminate cmd.exe when it's done its thing) which will call cscript.exe to run and interpret a .vbs. As you can see quite a few things that have to go right! :)
What if you 'wait' for the WScript.Shell.Run call to end (your $wait variable) before continuing execution of the wsh script which will in turn allow PHP to continue execution etc?
Since you're not waiting for the call to finish, PHP thinks its all good and continues onto the next line (interpreted language).
Also, maybe have the .vbs create an empty text file? Just so you have an indication that it has actually run.
Just take a step back, have a beer and it'll come to you! Gogo troubleshoot!
And - http://ss64.com/vb/run.html
If bWaitOnReturn is set to TRUE, the Run method returns any error code returned by the application.
I've tested your code with a stand-alone PHP script (without Codeigniter) on a Windows XP machine, with the PHP 5.4.4 built-in web server, and I've noticed that the vbscript gets executed, but PHP dies (the event viewer shows a generic "Application Error" with ID 1000).
However I've also discovered that removing the "cmd /C" from the command string solves the problem.
Here is the simple script that I've used for my test:
<?php
$obj = new COM('WScript.Shell');
if (is_object($obj)) {
//$obj->Run('cmd /C wscript.exe test.vbs', 0, true); // This does'nt work
$obj->Run('wscript.exe test.vbs', 0, true); // This works
var_dump($obj->Run);
} else {
echo 'can not create wshell object';
}
$obj = null;
?>
And this is my simple "test.vbs" script:
WScript.Echo "vbscript is running"
Another solution that seems to be working (at least on my platform) is the "system" call:
system('wscript.exe test.vbs');
Unfortunately I don't have a 64-bit IIS system to test with, so I can't really say if there are specific problems on this platform, but I hope this helps.

Stop PHP with ajax

I have a JavaScript functions which calls a PHP function through AJAX.
The PHP function has a set_time_limit(0) for its purposes.
Is there any way to stop that function when I want, for example with an HTML button event?
I want to explain better the situation:
I have a php file which uses a stream_copy_to_stream($src, $dest) php function to retrieve a stream in my local network. The function has to work until I want: I can stop it at the end of the stream or when I want. So I can use a button to start and a button to stop. The problem is the new instance created by the ajax call, in fact I can't work on it because it is not the function that is recording but it is another instance. I tried MireSVK's suggest but it doesn't worked!
Depending on the function. If it is a while loop checking for certain condition every time, then you could add a condition that is modifiable from outside the script (e.g. make it check for a file, and create / delete that file as required)
It looks like a bad idea, however. Why you want to do it?
var running = true;
function doSomething(){
//do something........
}
setInterval(function(){if(running){doSomething()}},2000); ///this runs do something every 2 seconds
on button click simply set running = false;
Your code looks like:
set_time_limit(0);
while(true==true){//infinite loop
doSomething(); //your code
}
Let's upgrade it
set_time_limit(0);
session_start();
$_SESSION['do_a_loop'] = true;
function should_i_stop_loop(){
#session_start();
if( $_SESSION['do_a_loop'] == false ) {
//let's stop a loop
exit();
}
session_write_close();
}
while(true==true){
doSomething();
should_i_stop_loop(); //your new function
}
Create new file stopit.php
session_start();
$_SESSION['do_a_loop'] = false;
All you have to do now is create a request on stopit.php file (with ajax or something)
Edit code according to your needs, this is point. One of many solutions.
Sorry for my English
Sadly this isn't possible (sort of).
Each time you make an AJAX call to a PHP script the script spawns a new instance of itself. Thus anything you send to it will be sent to a new operation, not the operation you had previously started.
There are a number of workarounds.
Use readystate 3 in AJAX to create a non closing connection to the PHP script, however that isn't supported cross browser and probably won't work in IE (not sure about IE 10).
Look into socket programming in PHP, which allows you to create a script with one instance that you can connect to multiple times.
Have PHP check a third party. I.E have one script running in a loop checking a file or a database, then connect to another script to modify that file or database. The original script can be remotely controlled by what you write to the file/database.
Try another programming language (this is a silly option, but I'm a fan of node). Node.js does this sort of thing very very easily.

Categories