Beanstalk/Pheanstalk not working in background - php

I am having troubles in setting up Pheanstalk on Ubuntu server.
I am relative new to programming, I have done all steps:
- installed beanstalk with sudo apt-get install beanstalk
- got pheanstalk from https://github.com/pda/pheanstalk/
And here is my code:
require_once('pheanstalk_init.php');
$pheanstalk = new Pheanstalk_Pheanstalk('127.0.0.1');
$pheanstalk
->useTube('testtube')
->put(exec("cat ../uploads/databases/app_data/Filename.sql | sqlite3 ../uploads/databases/Filename.sqlite"));
$job = $pheanstalk
->watch('testtube')
->ignore('default')
->reserve();
echo $job->getData();
$pheanstalk->delete($job);
The problem is that it takes 4-5 mins to run this code and for some reason the exec command does not run in background.
Any ideas on what am I doing wrong?
Many thanks in advance!

This is an old question, and probably you have solved it already ... anyway here is a short answer and more detailed explanation:
TL;DR : short answer: the problem was, that you run your (cpu/disk io) intensive code inside the put() method, and you needed to run it in the worker after the $job->getData() .
Explanation:
Beanstalk acts as a message queue, and allows you to decouple information producers from consumers. The idea is that you post just a description of the job that you want done to a queue and generating and posting that description to the queue is very quick. The actual resource (CPU/RAM/disk IO/network) consuming processing, happens in the consumer(s) (workers), when there are available ones, meanwhile the producer is free to do other stuff. If there are no workers available, then the jobs will just pile up in the queue.
So in your case, for example, separate your producer and consumers to chief.php and worker.php:
chief.php
<?php
require_once('pheanstalk_init.php');
$pheanstalk = new Pheanstalk_Pheanstalk('127.0.0.1');
$pheanstalk
->useTube('testtube')
->put("cat ../uploads/databases/app_data/Filename.sql | sqlite3 ../uploads/databases/Filename.sqlite");
?>
worker.php:
<?php
require_once('pheanstalk_init.php');
$pheanstalk = new Pheanstalk_Pheanstalk('127.0.0.1');
while(true){
$job = $pheanstalk->watch('testtube')->ignore('default')->reserve();
$cmd = $job->getData();
exec($cmd);
$pheanstalk->delete($job);
}
?>
So, when you run a worker, it will connect to the beanstalkd server, and wait for a job to be posted on the 'testtube' queue/tube . Then it will do the described job, and remove it from the queue, and loop, ready to process another job, when it arrives. You can run several workers in parallel too, if you want.
NB: The beanstalkd server does NOT run your workers; it only distributes jobs from producers to consumers. So you need to manage running worker processes yourself. I personally prefer to run workers as runit services, so that when there are any problem with them, they will be restarted by runit, and their output will be logged. You can of course use any process monitor tool you like: runit, systemd, monit, etc ...
It is also a good idea to run beanstalkd itself, under your favourite process monitor, just in case it crashes, which is unlikely, but can happen (it had happened to me an year ago).

Related

In the gearman client side, How do I remove a job which attempted to call a nonexisted function?

I'm using the Gearman php API.
In my client side, gclient.php:
$gClient = new GearmanClient();
$gClient->addServer('127.0.0.1', 4730);
$gClient->setTimeout(2000);
$ret = $gClient->doNormal('functionNameNonexist', json_encode(array(
'k' => 'v',
)));
$returnCode = $gClient->returnCode();
if ($returnCode != GEARMAN_SUCCESS)
{
echo "bad return code: {$returnCode}\n";
}
By running the command gearadmin --show-jobs, I can see the jobs queue became longer after each time the php script is executed.
Is there a way to remove/cancel those jobs that have been stayed in the queue over like 1 minute.
There is two fast ways to remove jobs in the queue:
Use the gearman CLI tool in worker mode to grab the jobs and send them to /dev/null:
gearman -n -w -f your_function_name > /dev/null
Restart gearmand. Note that if you are using a persistent job store, you will need to delete the job records from the table. If you're not using a persistent job store, be careful - you will lose all other jobs as well.

PHP Infinite script with Laravel

I have a table users that I need to upload continuously, so once updated, I would like to relaunch the command directly.
Actually, I'm using a cron that launch itself each minute with Laravel ($schedule->command('update:users')->everyMinute();), but I'm losing some time if the job is quicker than one minute of I will overload my server if it is more than one minute.
I was thinking to maybe use a queue, and once the script terminated, relauch itself, like this:
// Do My stuff
Queue::push(new UpdateUsers($));
But if the script crash, it will not reload itself, and I need to launch it at least once. I know that I could use a pcntl_fork function, but I would like to have a turnkey function with Laravel. How should I do ?
I would suggest running a Command the Cli,
in the command place a
while (true)
loop so it will run forever. After you created this script you can run it with supervisord
this service runs the command you tell him, and when it fails it will relaunch it automaticlly. Just be aware that after X failures it will stop, it depends on how you configured it.
Example for conf file in:
/etc/supervisord/conf.d/my_infinite_script.conf
and contents could be:
[program:laravel_queue]
command=php artisan your_command:here
directory=/path/to/laravel
autostart=true
autorestart=true
stderr_logfile=/var/log/your_command.err.log
stdout_logfile=/var/log/your_command.out.log
I've used the approach suggested by Tzook Bar Noy in some cases, but have also used a slightly uglier method which can avoid issues with having scripts looping forever if this might cause problems. This can be called every minute in a cronjob:
$runForSeconds = 55;
$runMinute = date('i');
do {
....code....
} while (date('i') == $runMinute && intval(date('s')) < $runForSeconds);
But the best solution would be to use a Jobs queue and run that using supervisor and:
command=php artisan queue:listen

Deleting queued jobs in laravel

I have added some jobs to a queue in Laravel. However, I forgot to put $job->delete() in the function and there is an error in my function. This means the job is never ending. It keeps going being replaced onto the queue and keeps erroring in my log file. How can I delete it from the command line?
I am using beanstalkd for my queuing.
I am using Redis instead of Beanstalkd but this should be the same in both. Restarting Redis doesn't solve the problem. I looked at RedisQueues in the Laravel 4.2 API Docs and found:
public Job|null pop(string $queue = null)
//Pop the next job off of the queue.
This is the same if you look at BeanstalkedQueue.
I threw it in app/routes.php inside dd*, loaded that page and voila.
Route::get('/', function() {
dd(Queue::pop());
#return View::make('hello');
});
NOTE: Reload the page once per queue.
The queue was pulled off the stack. I would like to see a cleaner solution but this worked for me more than once.
*dd($var) = Laravel's die and dump function = die(var_dump($var))
Edit 1: For Redis
The above obviously isn't the best solution so here is a better way. Be careful!
FLUSHDB - Delete all the keys of the currently selected DB. This command never fails.
For Redis use FLUSHDB. This will flush the Redis database not Laravel's database. In the terminal:
$ redis-cli
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> exit
Restart Beanstalk. On Ubuntu:
sudo service beanstalkd restart
I made an artisan command which will clear all the jobs in your queue. You can optionally specify the connection and/or the pipe.
https://github.com/morrislaptop/laravel-queue-clear
Important note: This solution works only for beanstalk
There are two solutions:
1- From Your PHP Code
To delete jobs programatically, you can do this:
//Que the job first. (YourJobProcessor is a class that has a method called fire like `fire($job,$data)`
$res = Queue::later(5, YourJobProcessor::class, $data, 'queue_name');
//get the job from the que that you just pushed it to
$job = Queue::getPheanstalk()->useTube("queue_name")->peek($res);
//get the job from the que that you just pushed it to
$res = Queue::getPheanstalk()->useTube("queue_name")->delete($job);
If everything went good, the job will not execute, else the job will execute after 5 seconds
2- From Command Line (Linux and Mac only)
From command line (In linux and mac) you can use beanstool.
For example, if you want to delete 100 ready jobs from the queue_name tube you can do the following:
for i in {1..100}; do beanstool delete -t queue_name --state=ready; done
For Redis users, instead of flushing, using redis-cli I ran this command:
KEYS *queue*
on the Redis instance holding queued jobs,
then deleted whatever keys in the response
DEL queues:default queues:default:reserved
Only way I could do it was to restart my computer. Could not find a way to delete a job.
I've used this php-based web admin console in the past.
Otherwise, I believe you'll find yourself using Terminal + telnet, altho I can't find any documentation for deleting via telnet (Just viewing a list of jobs in queue).
It seems that most articles tell you to use your code+library of choice and loop around queues jobs to delete them in this situation.
Here is Laravel 5.1 compatible command, which allows you to clear Beanstalkd queue. The command takes queue name as argument ('default' by default). Do not forget to register it in app/Console/Kernel.php

Codeigniter Daemon using System_Daemon package

I am trying to create a daemon using System_Daemon package with CodeIgniter's CLI. This is a new area for me and I'm struggling.
Here's what I have:
A CI controller that injects messages into an AWS SQS queue (Thanks to [url=http://codeigniter.com/forums/member/196201/]coccodrillo[/url] for providing excellent instructions on how to integrated AWS SDK into CI. See here: Integrating AWS SDK as a library in Codeigniter).
A CI controller that receives messages in the queue and writes it out to a log file and then deletes the message in the queue.
I would like to have a CI daemon which will listen to this queue, receive messages when they are there and do something useful with the message and then delete the message. So I started with the example in the documentation for System_Daemon and added in the CI code from receiving program. See code below
Is this the right thing to do? Can you guide me into doing this the "right way"? I've trolled the various knowledgeable forums and have come up short....Help me please!
Mmiz
#!/usr/bin/php -q
<?php
// Make it possible to test in source directory
// This is for PEAR developers only
ini_set('include_path', ini_get('include_path').':..');
// Include Class
error_reporting(E_ALL);
require_once "System/Daemon.php";
// Bare minimum setup
System_Daemon::setOption("appName", "receiveaws");
System_Daemon::setOption("logLocation","/tmp/log/receiveaws.log");
System_Daemon::setOption("appPidLocation","/tmp/log/receiveaws/receiveaws.pid");
System_Daemon::log(System_Daemon::LOG_INFO, "Daemon not yet started so this will be written on-screen");
// Spawn Deamon!
System_Daemon::start();
System_Daemon::log(System_Daemon::LOG_INFO, "Daemon: '".
System_Daemon::getOption("appName").
"' spawned! This will be written to ".
System_Daemon::getOption("logLocation"));
System_Daemon::log(System_Daemon::LOG_WARNING, 'My php code starting');
class Receiveaws extends CI_Controller {
public function index(){
if ($this->input->is_cli_request()) {
//Load the aws library
$this->load->library('awslib');
$sqs = new AmazonSQS();
//Get the queue to look at
$res=$sqs->get_queue_url('example-queue');
//Get the queue's url
$qurl=($res->body->GetQueueUrlResult->QueueUrl);
System_Daemon::log(System_Daemon::LOG_INFO,$qurl);
//Get a message from the queue
$response = $sqs->receive_message($qurl);
//If there was a message received, then do something
if ($res->isOK()) {
System_Daemon::log(System_Daemon::LOG_INFO,"Receive message successful");
//Now delete message from queue
$res=$sqs->delete_message($qurl,$rcpt_hand);
if ($res->isOK()) {
System_Daemon::log(System_Daemon::LOG_INFO,"Delete message successful");
}
} else {
//go back to check for messages
//How do you do that?
}
} else {
//Access from URL - so bail out?
//how do you not bail out of the daemon from here?
}
}
}
System_Daemon::stop();
?>
A daemon is a process running 'forever' in the background.
Here, all you do is checking for one new message in the queue, then you exit.
Basicly you have to add a loop that take all the code that need to be executed. You need to perform a sleep in the loop to, to avoid your daemon taking up all available resources.
Anyway, php isn't good at daemon because some memory is never freed until the end of the script. If your script never end (like a daemon), it'll eat up all available memory (according to php configuration) then die with an error. You will have to code you script very carefully to avoid such memory leaks!
Also, take note that each time you ask the sqs library something, it send a http request to Amazon servers. It can be very costly to do that too often.
To compensate, I recommend you to use a cronjob that run every minute to check for new task. This way you avoid memory leaks (php process goes down between executions) and too much network usage (request are made one ina minute).
On the last note, if you don't plan on having many tasks (that means, your daemon does nothing 99% of the time), consider using a push queue instead. With a push queue, it's not your script that poll the queue anymore, but the queue notify your script (ie: call you script with a standard http request) every time some task need to be done. This avoid running the script needlessy.
I don't know if amazon provides push queues, but ironmq (another 'free' queue service) can provide them.
More information: http://dev.iron.io/mq/reference/push_queues/

Forking in PHP on Windows

We are running PHP on a Windows server (a source of many problems indeed, but migrating is not an option currently). There are a few points where a user-initiated action will need to kick off a few things that take a while and about which the user doesn't need to know if they succeed or fail, such as sending off an email or making sure some third-party accounts are updated. If I could just fork with pcntl_fork(), this would be very simple, but the PCNTL functions are not available in Windows.
It seems the closest I can get is to do something of this nature:
exec( 'php-cgi.exe somescript.php' );
However, this would be far more complicated. The actions I need to kick off rely on a lot of context that already will exist in the running process; to use the above example, I'd need to figure out the essential data and supply it to the new script in some way. If I could fork, it'd just be a matter of letting the parent process return early, leaving the child to work on a few more things.
I've found a few people talking about their own work in getting various PCNTL functions compiled on Windows, but none seemed to have anything available (broken links, etc).
Despite this question having practically the same name as mine, it seems the problem was more execution timeout than needing to fork. So, is my best option to just refactor a bit to deal with calling php-cgi, or are there other options?
Edit: It seems exec() won't work for this, at least not without me figuring some other aspect of it, as it waits until the call returns. I figured I could use START, sort of like exec( 'start php-cgi.exe somescript.php' );, but it still waits until the other script finishes.
how about installing psexec and use the -d (don't wait) option
exec('psexec -d php-cgi.exe somescript.php');
Get PSExec and run the command:
exec("psexec -d php-cgi.exe myfile.php");
PSTools are a good patch in, but I'll leave this here:
If your server runs windows 10 and it has the latest updates, you can install a Linux subsystem, which has its own Kernel that supports native forking.
This is supported by Microsoft officially.
Here's a good guide on how to do it.
Once you've installed the subsystem itself, you need to install php on the subsystem.
Your windows "c:\" drive can be found under "/mnt/c", so you can run your php from the subsystem, which supports forking (and by extension the subsystem's php can use pcntl_fork).
Example: php /mnt/c/xampp/htdocs/test.php
If you want to run the subsystem's php directly from a windows command line you can simply use the "wsl" command.
Assuming you're running this from under "C:\xampp\htdocs\"
Example: wsl php main.php
The "wsl" command will resolve the path for you, so you don't need to do any dark magic, if you call the command under c:\xampp\htdocs, the subsystem will resolve it as "/mnt/c/xampp/htdocs/".
If you're running your server as an apache server, you don't really need to do anything extra, just stop the windows apache server and start the linux one and you're done.
Obviously you'll need to install all the missing php modules that you need on the subsystem.
You can create a daemon/background process to run the code (e.g. sending emails) and the request would just have to add items to the queue, let the deamon do the heavy lifting.
For example, a file send_emails.bat:
cls
C:\PHP533\php.exe D:\web\server.php
exit
open windows task scheduler, and have the above send_emails.bat run every 30 minutes. Make sure only one instance runs at a time or you might run each task in multiples, or send each email twice. I say 30 minutes in case something breaks temporarily (memory issues, database unavailable, etc), it will re-start every 30 minutes rather than having a never ending process that just stops. The following is a skeleton daemon... not complete or tested I am just typing out an example:
<?php
set_time_limit(60*30); // don't run
$keepgoing = true;
$timeout = time()+ 60*29; // 29 minutes
while(time() < $timeout)
{
// grab emails from database
$result = $db->query('select subject, body, to_email FROM email_queue');
if($result->num_rows == 0)
{
sleep(10); // so we are not taxing the database
}
else
{
while($row = $result->fetch_assoc())
{
// send email
}
}
}
exit;
?>
Finally you just need the request to add the item to the queue in a database, and let the daemon handle the heavy lifting.
$db->query('insert into email_queue(to,subject,body) values ('customer#email.com','important email','<b>html body!</b>');

Categories