Is it possible to reload a class into PHP? - php

The objective is to continually collect data of the current temperature. But a separate process should analyse the output of that data because I have to tweak the algorithm a lot but I want to avoid downtime so stopping the process is a no-go.
The problem is when I separate these processes, that process 2 would either continually have to make calls to the database or read from a local file to do something with the output generated by 1 process but I want to act upon it immediately and that is expensive in terms of resources.
Would it be possible to reload the class into memory somehow when the file changes by for example writing a function that keeps calculating the MD5 of the file, and if it changes than reload the class somehow? So this separate class should act as a plugin. Is there any way to make that work?

Here is a possible solution. Use Beanstalk (https://github.com/kr/beanstalkd).
PHP class to talk to Beanstalk (https://github.com/pda/pheanstalk)
Run beanstalk.
Create a process that goes into an infinite loop that reads from a Beanstalk queue. (Beanstalk queues are called "Tubes"). PHP processes are not meant to be run for a very long time. The main reason for this is memory. The easiest way to do handle this is to restart the process every once in a while or if memory gets to a certain threshold.
NOTE: What I do is to have the process exit after some fixed time or if it uses a certain amount of memory. Then, I use Supervisor to restart it.
You can put data into Beanstalk as JSON and decode it on the receiving end. The sending and receiving processes need to agree on that format. You could store your work payload in a database and just send the primary key in the queue.
Here is some code you can use:
class BeanstalkClient extends AbstractBaseQueue{
public $queue;
public $host;
public $port;
public $timeout;
function __construct($timeout=null) {
$this->loadClasses();
$this->host = '127.0.0.1';
$this->port = BEANSTALK_PORT;
$this->timeout = 30;
$this->connect();
}
public function connect(){
$this->queue = new \Pheanstalk\Pheanstalk($this->host, $this->port);
}
public function publish($tube, $data, $delay){
$payload = $this->encodeData($data);
$this->queue->useTube($tube)->put($payload,
\Pheanstalk\PheanstalkInterface::DEFAULT_PRIORITY, $delay);
}
public function waitForMessages($tube, $callback=null){
if ( $this->timeout ) {
return $this->queue->watchOnly($tube)->reserve($this->timeout);
}
return $this->queue->watchOnly($tube)->reserve();
}
public function delete($message){
$this->queue->delete($message);
}
public function encodeData($data){
$payload = json_encode($data);
return $payload;
}
public function decodeData($encodedData) {
return json_decode($encodedData, true);
}
public function getData($message){
if ( is_string($message) ) {
throw new Exception('message is a string');
}
return json_decode($message->getData(), true);
}
}
abstract class BaseQueueProcess {
protected $channelName = ''; // child class should set this
// The queue object
public $queue = null;
public $processId = null; // this is the system process id
public $name = null;
public $status = null;
public function initialize() {
$this->processId = getmypid();
$this->name = get_called_class();
$this->endTime = time() + (2 * 60 * 60); // restart every hour
// seconds to timeout when waiting for a message
// if the process isn't doing anything, timeout so they have a chance to do housekeeping.
$queueTimeout = 900;
if ( empty($this->queue) ) {
$this->queue = new BeanstalkClient($queueTimeout);
}
}
public function receiveMessage($queueMessage) {
$taskData = $this->queue->getData($queueMessage);
// debuglog(' Task Data = ' . print_r($taskData, true));
if ( $this->validateTaskData($taskData) ) {
// process the message
$good = $this->didReceiveMessage($taskData);
if ( $good !== false ) {
// debuglog("Completing task {$this->taskId}");
$this->completeTask($queueMessage);
}
else {
$this->failTask($queueMessage);
}
}
else {
// Handle bad message
$this->queue->delete($queueMessage);
}
}
public function run() {
$this->processName = $this->channelName;
// debuglog('Start ' . $this->processName);
// debuglog(print_r($this->params, true));
while(1) {
$queueMessage = $this->queue->waitForMessages($this->channelName);
if ( ! empty($queueMessage) ) {
$this->receiveMessage($queueMessage);
}
else {
// empty message
// a timeout
// // debuglog("empty message " . get_called_class());
}
$memory = memory_get_usage();
if( $memory > 20000000 ) {
// debuglog('Exit '.get_called_class().' due to memory. Memory:'. ($memory/1024/1024).' MB');
// Supervisor will restart process.
exit;
}
elseif ( time() > $this->endTime ) {
// debuglog('Exit '.get_called_class().' due to time.');
// Supervisor will restart process.
exit;
}
usleep(10);
}
}
public function completeTask($queueMessage) {
//
$this->queue->delete($queueMessage);
}
public function failTask($queueMessage) {
//
$this->queue->delete($queueMessage);
}
}
class MyProcess extends BaseQueueProcess {
public function initialize() {
$this->channelName = 'Temperature';
parent::initialize();
}
public function didReceiveMessage($taskData) {
// debuglog(print_r($taskData, true));
// process data here
// return false if something went wrong
return true;
}
}
//Sender
class WorkSender {
const TubeName = 'Temperature';
const TubeDelay = 0; // Set delay to 0, i.e. don't use a delay.
function send($data) {
$c = BeanstalkClient();
$c->publish(self::TubeName, $data, self::TubeDelay);
}
}

Related

Botman yii2. Properties of the conversation lost

BotMan Version: 2.1
PHP Version:7.3
Messaging Service(s):
Cache Driver: SymfonyCache
Description:
I trying to have conversation. In every next method I lost data from conversation properties, that was saved in properties before!
class GetAlertDataConversation extends AppConversation
{
public $bot;
public $alertTitle;
public $alertDescription;
public $alertLatitude;
public $alertLongitude;
public $alertAuthorId;
public $alertAuthorName;
public function __construct($bot)
{
$this->bot = $bot;
}
private function askTitle()
{
$this->ask('Что случилось? (кратко)', function (Answer $answer) {
$this->alertTitle = $this->askSomethingWithLettersCounting($answer->getText(), 'Слишком коротко!', 'askDescription');
\Yii::warning($this->alertTitle);
});
}
public function askDescription()
{
$this->ask('Расскажи подробней!', function (Answer $answer) {
$this->alertDescription = $this->askSomethingWithLettersCounting($answer->getText(), 'Слишком коротко!', 'askLocation');
\Yii::warning($this->alertTitle);
});
}
private function askLocation()
{
\Yii::warning($this->alertTitle);
$this->askForLocation('Локация?', function (Location $location) {
// $location is a Location object with the latitude / longitude.
$this->alertLatitude = $location->getLatitude();
$this->alertLongitude = $location->getLongitude();
$this->endConversation();
return true;
});
}
private function endConversation()
{
\Yii::warning($this->alertTitle);
$alertId = $this->saveAlertData();
if ($alertId)
$this->say("Событие номер {$alertId} зарегистрировано!");
else
$this->say("Ошибка при сохранении события, обратитесь к администратору!");
}
private function saveAlertData()
{
$user = $this->bot->getUser();
$this->alertAuthorId = $user->getId();
$this->alertAuthorName = $user->getFirstName() . ' ' . $user->getLastName();
$alert = new Alert();
\Yii::warning($this->alertTitle);
$alert->name = $this->alertTitle;
$alert->description = $this->alertDescription;
$alert->latitude = $this->alertLatitude;
$alert->longitude = $this->alertLongitude;
$alert->author_id = $this->alertAuthorId;
$alert->author_name = $this->alertAuthorName;
$alert->chat_link = '';
$alert->additional = '';
if ($alert->validate()) {
$alert->save();
return $alert->id;
} else {
\Yii::warning($alert->errors);
\Yii::warning($alert);
return false;
}
}
}
There is user's text answer in the first \Yii::warning($this->alertTitle); in the askTitle() function.
But all other \Yii::warning($this->alertTitle); returns NULL!!!!
As the result, saving of Alert object not working!
Please, help me. Some ideas?
I think, that it can be by some caching + serialise problem.
I was trying to change cache method to Redis. Same result.
Problem was in this function call and caching:
$this->askSomethingWithLettersCounting($answer->getText(), 'Слишком коротко!', 'askDescription');
If you will read other conversation botman issues in github, you wil see, that most of issues in conversation cache and serialise.
Not any PHP code of conversation can be cached right.
In this case, not direct call of functions askDescription() and askLocation() broken conversation caching.
I fixed that by removing askSomethingWithLettersCounting() function.

Can't run more than 3 gearman workers on same server

When i run gearman upto three workers on single server is working fine but when i start the 4th one php code is working fine but can't detect the new worker and also not clearing the job queue.
protected function createWorker()
{
$this->worker = new \GearmanWorker();
$config = $this->app->config->job_remote;
$this->worker->addServer($config['host'], $config['port']);
return $this->worker;
}
public function listen($eventType, $callback)
{
if (!($this->worker instanceof \GearmanWorker)){
$this->worker = $this->createWorker();
}
$this->worker->addFunction($eventType, $callback);
return $this->worker;
}
public function doWork($worker)
{
if (!($worker instanceof \GearmanWorker)){
$worker = $this->createWorker();
}
$this->worker = $worker;
while (1) {
$this->worker->work();
$this->app->log->debug($this->worker->returnCode());
if ($this->worker->returnCode() != \GEARMAN_SUCCESS) {
break;
}
}
}
First I am calling 'listen' method and then 'doWork' method
Client Side Code:
protected function createClient()
{
$this->client = new \GearmanClient();
$config = $this->app->config->job_remote;
$this->client->addServer($config['host'], $config['port']);
return $this->client;
}
public function addTask($eventType, array $params)
{
if (!($this->client instanceof \GearmanClient)){
$this->client = $this->createClient();
}
// add single task in queue
$this->client->addTaskBackground($eventType, serialize($params));
// Run task
$this->client->runTasks();
}
Using GearmanManager You can run upto any no. of workers untill it slows down your computer and uses 100% cpu and 100% memory.
Gearman.ini
[GearmanManager]
worker_dir=./workers
count=50
dedicated_count=1
max_worker_lifetime=3600
auto_update=1
log_file=./logs/WebAnalyzerWorker.log
max_runs_per_worker=3
timeout=600
Run Gearman worker
./vendor/brianlmoon/gearmanmanager/pecl-manager.php -c ./gearman.ini -vvvvv
Worker CLass
<?php
include dirname(__DIR__)."/vendor/autoload.php";
use Services\Log;
use Services\ServiceInitialization;
use Lib\Technology\FindWhoisRecords;
use Illuminate\Database\Capsule\Manager as Capsule;
class DomainDetailFetchJob{
public function beforeRun()
{
ServiceInitialization::loadConfig();
ServiceInitialization::loadDatabaseConfiguration();
}
public function run($job, &$log)
{
$log[] = "Starting whois job";
$collection = "whois";
$this->beforeRun();
ServiceInitialization::$config->all();
$workload = $job->workload();
//workload
$whois = new FindWhoisRecords($workload);
if($whois->whois_result["err_code"]==0) {
$log[] = "Whois Info fetch successful";
//success save the details
$whois->whois_result["result"]["workload"] = $workload;
Capsule::table($collection)->where("workload", $workload)->update($whois->whois_result["result"], ['upsert' => true]);
}
else {
$log[] = "Whois Info fetch failed";
$logger = new Log();
$logger->write($whois->whois_result["err_msg"], "error", "Whois Record Job");
unset($logger);
}
}
}
Gearman Client
$client = new GearmanClient();
$client->addServer();
$client->doBackground("DomainDetailFetchJob", $url);

Subscribing and reading data from redis channels using php-redis simultaneously

I have to subscribe to all the channels of my Redis db and simultaneously read data from another hash in the same db node. The following is the code I have written for this using phpredis:
$notif = new Worker;
try {
// connect redis
$notif->connectRedisServer();
// start the worker processing
$notif->startWorkerProcess();
}
catch(Exception $e) {
exit('Exception: '.$e->getMessage());
}
class Worker {
private $redis;
public $recentHash = 'tracker.data.recent';
public $notifHash = 'tracker.settings.data';
public $serverConnectionDetails = { //Redis server connection details
};
private $redis;
// redis database used for getting recent record of every imei
const RECENTDATA_DB = 1;
public function connectRedisServer() {
if(!empty($this->redis))
return $this->redis; // already connected
$this->redis = new Redis();
if(!$this->redis->pconnect(
$this->serverConnectionDetails['redis']['host'],
$this->serverConnectionDetails['redis']['port']
))
return 'Redis connection failed.';
if(isset($this->serverConnectionDetails['redis']['password'])
&&
!$this->redis->auth($this->serverConnectionDetails['redis']['password'])
)
return 'Redis authentication failed.';
return $this->redis;
}
public function startWorkerProcess() {
$this->redis->select(self::RECENTDATA_DB);
$this->redis->pSubscribe(array('*'), array($this, 'processRedisData'));
}
public function processRedisData($redis, $pattern, $chan, $msg) {
$message = rtrim((string) $msg,",");
$tData = json_decode($message, true);
$tId = (int) $tData['tID'];
echo "Recent Published Data:";
var_dump($tData);
$data = $this->getNotifSettings($tId);
if(!empty($data)) {
echo "Redis Settings: ";
var_dump($trackerSettings);
}
}
public function getNotifSettings($tId) {
$data = $this->redis->hGet($this->notifHash, $tId); //This command doesn't return any data even if it exists for the $tId
if($data === false)
return null;
$data = json_decode($data, true);
return $data; // Always comes up as null
}
}
The problem here is that once I get subscribed to all the channels on db1 in Redis. I don't get any result if I try to run HGET, even though the data for the given key exists in the db. I have put additional comments in the code above to explain where the problem is. Check getNotifSettings() function.
Any help will be appreciated.

Asterisk AMI PHP via fsockopen and socket_get_status. socket_get_status returns unread bytes earlier than it is

I'm using custom class to connect to asterisk server via php.
Here it's code:
class Asterisk_ami
{
public $ini = array();
function __construct ()
{
$this->ini["con"] = false;
$this->ini["host"] = "127.0.0.1";
$this->ini["port"] = "****";
$this->ini["lastActionID"] = 0;
$this->ini["lastRead"] = array();
$this->ini["sleep_time"]=1.5;
$this->ini["login"] = "****";
$this->ini["password"] = "****";
}
function __destruct()
{
unset ($this->ini);
}
public function connect()
{
$this->ini["con"] = fsockopen($this->ini["host"], $this->ini["port"],$a,$b,10);
if ($this->ini["con"])
{
stream_set_timeout($this->ini["con"], 0, 400000);
}
}
public function disconnect()
{
if ($this->ini["con"])
{
fclose($this->ini["con"]);
}
}
public function write($a)
{
$this->ini["lastActionID"] = rand (10000000000000000,99999999900000000);
fwrite($this->ini["con"], "ActionID: ".$this->ini["lastActionID"]."\r\n$a\r\n\r\n");
$this->sleepi();
return $this->ini["lastActionID"];
}
public function sleepi ()
{
sleep($this->ini["sleep_time"]);
}
public function read()
{
$mm = array();
$b = array();
$mmmArray=array();
$k = 0;
$s = "";
$this->sleepi();
do
{
$s.= fread($this->ini["con"],1024);
sleep(0.005);
$mmm=socket_get_status($this->ini["con"]);
array_push($mmmArray, $mmm);
} while ($mmm['unread_bytes']);
$mm = explode ("\r\n",$s);
$this->ini["lastRead"] = array();
for ($i=0;$i<count($mm);$i++)
{
if ($mm[$i]=="")
{
$k++;
}
$m = explode(":",$mm[$i]);
if (isset($m[1]))
{
$this->ini["lastRead"][$k][trim($m[0])] = trim($m[1]);
}
}
unset ($b);
unset ($k);
unset ($mm);
unset ($mm);
unset ($mmm);
unset ($i);
unset ($s);
var_dump($mmmArray);
return $this->ini["lastRead"];
//return $s;
}
public function init()
{
return $this->write("Action: Login\r\nUsername: ".$this->ini["login"]."\r\nSecret: ".$this->ini["password"]."\r\n\r\n");
}
}
And here is testAsterisk.php where I try it.
include("./lib/asteriskAmi.php");
$a = new Asterisk_ami();
$a->connect();
if ($a->ini["con"])
{
$a->init();
$a->write("Action: GetConfig\r\nFilename: extensions.conf\r\n");
print_r($a->read());
$a->disconnect();
}
I want to get extension.conf config via ami. The problem is that I don't get full config. 16 last string are alwas missing. However when I check GetConfig via asterisk console it returns full config.
As you can see while cycle is interrupted when unread bytes of socket_get_status are 0, I checked them pushing in array and dumping it, and I can see that unread_bytes are actually 0. I tried changing sleep_time and different timeout parametres, the result was the same.
What else can I check? What can cause this mistake? May be I can use some other func?
Really, only proper solution I've found is to use PAMI, instead of custom class. It's more comfortable for using and anyway it gives me full content of extensions.conf.

PHP testing between pthreads and curl

We are planning to building real time bidding and we are evaluating performance of PHP compare to Java in terms of throughput/response times etc.
(Java part is taken care by other member of team)
Initial start:
I have a test script which makes 50 http connection to different servers.
1st approach
- I am using curl_multi_init function and I get response under 7 seconds.
2nd approach
- I am using PHP pthreads api and trying to make parallel calls and expecting same response time or less.But total time on average is around 25 seconds
Here is the code
<?php
$g_request_arr = array(
'0' => array(
'request_url' => 'https://www.google.co.uk/?#q=56%2B12'
),
..
..
..
'49'=>array(
'request_url' => 'https://www.google.co.uk/?#q=256%2B132'
)
);
class ChildThread extends Thread {
public function __construct($urls) {
$this->data = $urls;
}
public function run(){
foreach($this->data as $url_info ){
$url = $url_info['request_url'];
file_get_contents($url);
}
$this->synchronized(function($thread){
$thread->notify();
}, $this);
}
}
$thread = new ChildThread($g_request_arr);
$thread->start();
$thread->synchronized(function($thread){
}, $thread);
?>
I want to know what is missing in above code or is it possible to bring the response under 7 seconds.
You are requesting all the data in one thread, here's a better approach:
<?php
class WebRequest extends Stackable {
public $request_url;
public $response_body;
public function __construct($request_url) {
$this->request_url = $request_url;
}
public function run(){
$this->response_body = file_get_contents(
$this->request_url);
}
}
class WebWorker extends Worker {
public function run(){}
}
$list = array(
new WebRequest("http://google.com"),
new WebRequest("http://www.php.net")
);
$max = 8;
$threads = array();
$start = microtime(true);
/* start some workers */
while (#$thread++<$max) {
$threads[$thread] = new WebWorker();
$threads[$thread]->start();
}
/* stack the jobs onto workers */
foreach ($list as $job) {
$threads[array_rand($threads)]->stack(
$job);
}
/* wait for completion */
foreach ($threads as $thread) {
$thread->shutdown();
}
$time = microtime(true) - $start;
/* tell you all about it */
printf("Fetched %d responses in %.3f seconds\n", count($list), $time);
$length = 0;
foreach ($list as $listed) {
$length += strlen($listed["response_body"]);
}
printf("Total of %d bytes\n", $length);
?>
This uses multiple workers, which you can adjust by changing $max. There's not much point in creating 1000 threads if you have 1000 requests to process.

Categories