I am using pcntl in order to speed up a quite heave CLI php script, that consists mostly of a class, that is in charge of sending all of the auto-emailing on my application.
My goal is as following: I want to assign each process to a certain task, within a foreach loop, the implementation I've used is the one shown in the code example below.
The problem is that once you fork a process, it executes asynchronously, and also gets a copy of the parent's process stack. In my case, what happens is that one task simply executes several times, My question is how can I design this script to be smarter in order to avoid such behavior?.
Code:
/**
@description this is the main procedure of this class, it iteratates over the relevant tasks and sends the emails using the SendGrid wrapper class
@see SendGridWrapper
@return void
*/
public function execute(){
if(!isset($this->tasks)){
throw new exception("Please call getRelevantTasks() prior to trying to execute anything");
}
$proccesses = array();
foreach($this->tasks as $myTask){
$pid = pcntl_fork();
if($pid){
$proccesses[] = $pid;
}
else if($pid == -1){
die('FORK FAILED, STATUS -1');
}
else{
if(isset($myTask['recipient_model'])){
$this->currentModel = $myTask['recipient_model'];
$lang = $myTask['lang'];
$classPath = self::$modelsDir . $myTask['recipient_model'] . '.php';
$className = $myTask['recipient_model'];
if(!class_exists($myTask['recipient_model'] )){
require_once(dirname(__FILE__) . '/../' . $classPath);
}
else if(isset($recipientFetcher)){
unset($this->model);
unset($this->mailingList);
unset($this->substitutionList);
}
$this->model = null;
$this->mailingList = null;
$this->substitutionList = null;
$this->model = new $className($myTask['lang']);
$addresses = $this->model->getMailRecipients();
if(empty($addresses) || sizeof($addresses) == 0){
continue;
}
$this->model->prepare();
$this->substitutionList = $this->model->getDynamicParams();
}
else{
throw new exception('No recipient model was found');
}
foreach($addresses as $myMail){
$this->mailingList[$myMail['personal_email']] = $myMail['contact_name'];
}
$templatePath = dirname(__FILE__) . '/../';
$templatePath .= $lang ? self::$templatesDirEn . $myTask['html_email_path'] : self::$templatesDirHe . $myTask['html_email_path'];
$html = file_get_contents($templatePath);
$this->sendMail($html, $myTask['task_schedule_id']);
echo "model:" . $myTask['recipient_model'];
echo $this->log;
$this->log = "";
die("\r\n Child proccess has been executed successfully\r\n");
}
}
if($pid){
foreach($proccesses as $key => $val){
pcntl_waitpid($val, $status, WUNTRACED);
}
}
}
Thanks in advance, Oleg.
Introduction
I see you are trying to send mails
$this->sendMail($html, $myTask['task_schedule_id']);
and I think it's a really bad idea trying to use multiple process for this task. You should consider using message queue for this task because emails can be very slow.Use a Queue System
You should be using Gearman, ZeroMQ or Beanstalkd for this task. Worst case scenario use Implement your own simple message queue with
memcached
.Here is a typical Gearman Example: https://stackoverflow.com/questions/13855907/when-to-send-auto-email-instantly-on-button-click-or-later
Quick Fix
Remove all those code and put it in a function called
execute_worker
where you can push the task to itUsing Threads
You can also use Worker or Thread in
PHP
with pThreads to speed up processing.Simple Project
file_get_contents
is said to be slow when compared withcurl
and no where close to the power ofcurl_multi_init
which allows the processing of multiple cURL handles in parallel.See:
Our Objective would be to implement our own Multi
file_get_contents
versionMulti file_get_contents Example
Output
Time Taken
Classes Used
Conclusion
Did
file_get_contents
just get100 pages
in just1.489 sec
with my crappy connection.Yes
it did. Tested the same code on my live server and It took me less than0.939 sec
to fetch200
pages.Your application can be faster in so many ways you just have to use the right technology at the right place.