"start_batch was called incorrectly" on gRPC unary call - php

I make an unary call from PHP code like that:
public function getByIDs(array $ids): array
{
$request = new GetByIDsRequest();
$request->setIDs($ids);
$grpcRequest = $this->client->GetByIDs($request, [], ['timeout' => $this->waitTimeout]);
$response = $this->callWithRetries($grpcRequest);
if (!$response->isOk()) {
$status = $response->getStatus();
throw new GrpcServerException(__FUNCTION__, $status->getDetails(), (int)$status->getCode());
}
... // business logic here
}
private function callWithRetries(\Grpc\UnaryCall $grpcRequest): GrpcUnaryCallResponse
{
$response = null;
for ($attempt = 0; $attempt <= self::$maxRetries; $attempt++) {
if ($attempt > 0) {
usleep(mt_rand(1, 5) * self::$waitBeforeRetry);
}
$response = $this->unaryCall($grpcRequest);
$status = $response->getStatus()->getCode();
if ($response->isOk() || !self::isRetryable($status)) {
return $response;
}
}
return $response;
}
protected function unaryCall(UnaryCall $call): GrpcUnaryCallResponse
{
[$response, $status] = $call->wait();
return new GrpcUnaryCallResponse(
$response,
new GrpcStatus($status)
);
}
Sometimes I get the LogicException with message "start_batch was called incorrectly". Any thoughts how to deal with that exception and what is the root cause?

After some investigation I found out that the problem occurs because of reusing the instance of \Grpc\UnaryCall in retries. You must not do that otherwise you get a core dump.
The reason we got the LogicException but not a core dump is using a patched gRPC extension.
Some more technical details you can find in this issue

Related

Exception Serialization of 'Closure' is not allowed

I am trying to cache a sitemap generated from a controller for a website that I am working on, but apparently I am doing something wrong, as I don't understand the Error message.
Here is the code snippet causing the trouble (it is a controller Method). Everything works correctly until I add the caching.
public function mapContent($type, Request $request)
{
$cachingKey = $request->fullUrl();
if ($type !== 'news' && $type !== 'pages') {
abort(404);
} elseif (Cache::has($cachingKey)) {
$items = Cache::get($cachingKey);
} else {
$items = $this->_getElementsSitemap($type);
Cache::put($cachingKey, $items, now()->addDays(7));
}
return Response::make($items, '200')->header('Content-Type', 'application/xml');
}
Seems that $items = $this->_getElementsSitemap($type); returns not serializable instance.
Your class should implement __serialize method

session_set_start_handler throwing warnings

I am writing my own session handler as below to make Aerospike as the session manager. However, it is throwing warnings about session handler.
Warning: session_start(): Cannot find save handler '' - session startup failed in /var/www/session.php on line 165
I have set the values of session.save_path and session.save_handler to "" in php.ini as I want to manage the session data storage and retrieval myself with the below class.
NOTE: I can use the default aerospike session handling but it saves session data as bytes(hexadecimal format), I can't use it as it is as other applications need to read this data as well, so I am trying to save data as a json encoded string.
A weird behavior is that the close method always gets called at script shutdown even though the session_set_save_handler's return value is false!
This works sometimes without error and sometimes throws a warning shown above. Not sure what is missing.
Code:
<?php
ini_set( 'display_errors', 1 );
ini_set( 'error_reporting', E_ALL );
define('SESS_ID', '66ac548234f96b48b42e18b2d3d7b73a3f1aceb01fa4c20647d3dcaa055b5099');
class MySessionHandler implements SessionHandlerInterface {
private $database = null;
private $recordKey = null;
public function __construct(){
$this->init();
}
private function init() {
$this->database = new \Aerospike(
[
"hosts" => [
0 => [
"addr" => "IP_HERE",
"port" => 3000
]
]
], false);
$this->recordKey = $this->database->initKey( 'cache', 'data', SESS_ID);
}
private function isConnected() : bool {
return ( $this->database instanceof \Aerospike ) && $this->database->isConnected();
}
public function open($savepath = '', $id = ''){
// If successful
if( is_null($this->database) ) {
$this->init();
}
$status = $this->database->get($this->recordKey, $data);
if ($status == \Aerospike::OK) {
$data = json_decode($data['bins']['PHP_SESSION'], 1);
if( !is_array($data) ) {
$data = [];
}
} else {
$data = [];
}
return true;
}
public function read($id)
{
if( is_null($this->database) ) {
$this->init();
}
$status = $this->database->get($this->recordKey, $data);
if ($status == \Aerospike::OK) {
$data = json_decode($data['bins']['PHP_SESSION'], 1);
if( !is_array($data) ) {
$data = [];
}
} else {
$data = [];
}
$_SESSION = $data;
return json_encode($data);
}
public function write($id, $dataNode)
{
if( is_null($this->database) ) {
$this->init();
}
$data = false;
if( $this->isConnected() ) {
$bins = [
"PHP_SESSION" => json_encode($_SESSION)
];
$status = $this->database->put( $this->recordKey, $bins );
if ($status == \Aerospike::OK) {
$data = true;
} else {
// error while saving data, log it
}
}
return $data;
}
public function destroy($id)
{
$removeStatus = false;
if( $this->isConnected() ) {
$status = $this->database->remove( $this->recordKey );
if ($status == \Aerospike::OK) {
$removeStatus = true;
} else {
// error while saving data, log it
}
}
return $removeStatus;
}
public function close(){
// Close the database connection
if($this->isConnected() && $this->database->close()){
// Return True
return true;
}
// Return False
return false;
}
public function gc($max)
{
return 0;
}
public function __destruct()
{
$this->close();
}
}
$s = new MySessionHandler();
// Set handler to overide SESSION
$newSession = session_set_save_handler($s, true);
var_dump($newSession); // this returns false sometimes and throws a warning
register_shutdown_function('session_write_close');
session_id(SESS_ID);
session_start();
$_SESSION['dfc'] = 'xdc1';
//unset($_SESSION['dfc']);
pr($_SESSION);
unset($s);
function pr($data) {
if( is_object($data) ) {
// $data = serialize($data);
}
echo '<pre>' . var_export($data, 1) . '</pre>';
}
These warnings are thrown intermittently and I am not sure what might be causing this. Any help would be appreciated.
The PHP client for Aerospike comes with a session handler. Set session.save_handler=aerospike. See the php.ini options of the module.
If you're writing your own session handler
Don't set session.save_handler=''. You want to comment it out in your php.ini instead, because that gets loaded and executed first (before your script) and it's non-sensical. There is no such handler. That should suppress the warning.
Watch out for null bytes. PHP strings can have those in the middle, but Aerospike strings will terminate there, so it'll get truncated at that point. Read the documentation on Handling Unsupported Types, which is why you're provided the \Aerospike\Bytes wrapper class.

PHP create override function only for certain parts of the original function

Hi there and welcome to my first official problem! I'm trying to override a function, but only certain parts. The function i want to override, is different in several versions of the open source CMS system. The specific parts which i need to change in order for this to work, are the same in all of those versions.
The override has to work with all of those different versions of the CMS system.
I can't use PECL (apd) package or any other external PHP package for this. And as far i know and could find in the PHP manual, there is no function for this.
So, for example:
We have the different CMS system versions: 1, 2 and 3.
And we would have a original function like this one (THIS IS JUST AN EXAMPLE AND NOT THE ACTUAL FUNCTION I NEED CHANGED), which can only be used for version 3. Certain parts are the same in all versions, and some parts are different:
public function createAccessToken($body = false)
{
if (!$body) { //this is different
$body = 'grant_type=client_credentials'; //this is different
}
$this->action = 'POST'; //this is different
$this->endpoint = 'v1/oauth2/token'; //this is different
$response = $this->makeCall($body, "application/json", true); //this is different
if (!isset($response->access_token)) { //this is THE SAME
return false; //this is THE SAME
}
$this->token = $response->access_token; //this is different
return true; //this is different
}
And this is what i would like to change for all of those versions:
public function createAccessToken($body = false)
{
if (!$body) {
$body = 'grant_type=client_credentials';
}
$this->action = 'POST';
$this->endpoint = 'v1/oauth2/token';
$response = $this->makeCall($body, "application/json", true);
if (isset($response->access_token)) { //IT'S CHANGED! THE -> ! IS GONE
return false;
}
$this->token = $response->access_token;
return true;
}
But the function above (which has changed), will only work with version 3 of the CMS system.
Therefore, is there any way i can only override the specific part i need to change and "get" the code which doesn't have to change some other way so the function would still be executed? So again:
public function createAccessToken($body = false)
{
//some way to get the code above the part i need to change, untill i get to the part which needs to be changed. So the override "takes over" from here.
if (isset($response->access_token)) { //IT'S CHANGED! THE -> ! IS GONE
return false;
}
//some way to get the rest of the code and have the function continue agian
}
Hope you can help me out on this.
You could create your own middle layer. This is assuming you have access to some variable that defines your current VERSION.
public function createAccessToken($body = false) {
if (VERSION == 1 || VERSION == 2) { createAccessTokenOld($body); }
else { createAccessTokenNew($body); }
}
public function createAccessTokenOld($body = false)
{
if (!$body) {
$body = 'grant_type=client_credentials';
}
$this->action = 'POST';
$this->endpoint = 'v1/oauth2/token';
$response = $this->makeCall($body, "application/json", true);
if (!isset($response->access_token)) { //IT'S CHANGED! THE -> ! IS GONE
return false;
}
$this->token = $response->access_token;
return true;
}
public function createAccessTokenNew($body = false)
{
if (!$body) {
$body = 'grant_type=client_credentials';
}
$this->action = 'POST';
$this->endpoint = 'v1/oauth2/token';
$response = $this->makeCall($body, "application/json", true);
if (isset($response->access_token)) { //IT'S CHANGED! THE -> ! IS GONE
return false;
}
$this->token = $response->access_token;
return true;
}
You could also do it with a bit more code reuse:
public function createAccessToken($body = false) {
if (VERSION == 1 || VERSION == 2) { createAccessToken($body, true); }
else { createAccessToken($body, false); }
}
public function createAccessToken($body = false, $isOld = false)
{
if (!$body) {
$body = 'grant_type=client_credentials';
}
$this->action = 'POST';
$this->endpoint = 'v1/oauth2/token';
$response = $this->makeCall($body, "application/json", true);
if ($isOld) { if (!isset($response->access_token)) { return false; } }
else { if (isset($response->access_token)) { return false; } }
$this->token = $response->access_token;
return true;
}

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.

Logging SOAP envelope in third party library

I am attempting to add logging for the envelope generated by a third party library. I am modifying the updateMetadataField() method below.
I am creating $client like so:
$client = new UpdateClient($UPDATE_END_POINT, $USER_AUTH_ARRAY);
I have tried both $this->client->__getLastRequest() and $this->__getLastRequest() with the same error as a result.
When the SoapClient is instantiated trace is set to true.
Error is
Fatal error: Call to undefined method UpdateClient::__getLastRequest()
So how do I correctly access the __getLastRequest() method?
$USER_AUTH_ARRAY = array(
'login'=>"foo",
'password'=>"bar",
'exceptions'=>0,
'trace'=>true,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS
);
class UpdateClient {
private $client;
public function __construct($endpoint, $auth_array) {
$this->client = new SoapClient($endpoint, $auth_array);
}
public function updateMetadataField($uuid, $key, $value) {
$result = $this->client->updateMetadataField(array(
'assetUuid' => $uuid,
'key' => $key,
'value' => $value)
);
if(is_soap_fault($result)) {
return $result;
}
return $result->return . "\n\n" . $this->client->__getLastRequest();
} // updateMetadataField()
} // UpdateClient
UPDATE - adding calling code This code iterates over an array which maps our data to the remote fields.
What I am hoping to do is begin storing the envelope we send to aid in debugging.
$client = new UpdateClient($UPDATE_END_POINT, $USER_AUTH_ARRAY);
foreach ($widen_to_nool_meta_map as $widen => $nool) { // array defined in widen.php
if ($nool != '') {
// handle exceptions
if ($nool == 'asset_created') { // validate as date - note that Widen pulls exif data so we don't need to pass this
if (!strtotime($sa->$nool)) {
continue;
}
} else if ($nool == 'people_in_photo' || $nool == 'allow_sublicensing' || $nool == 'allowed_use_pr_gallery') {
// we store as 0/1 but GUI at Widen wants Yes/No
$sa->$nool = ($sa->$nool == '1') ? 'Yes' : 'No';
} else if ($nool == 'credit_requirements') {
$sa->$nool = $sa->credit_requirements()->label;
}
$result = $client->updateMetadataField($sa->widen_id, $widen, $sa->$nool);
if(is_soap_fault($result)) {
$sync_result = $sync_result . "\n" . $result->getMessage();
} else {
$sync_result = $sync_result . "\n" . print_r($result, 1);
}
} // nool field set
} // foreach mapped field
If you want to access UpdateClient::__getLastRequest() you have to expose that method on the UpdateClient class since the $client is a private variable. The correct way of calling it is $this->client->__getLastRequest().
Take a look at this working example, as you can see I'm consuming a free web service for testing purposes.
<?php
$USER_AUTH_ARRAY = array(
'exceptions'=>0,
'trace'=>true,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS
);
class TestClient {
private $client;
public function __construct($endpoint, $auth_array) {
$this->client = new SoapClient($endpoint, $auth_array);
}
public function CelsiusToFahrenheit( $celsius ) {
$result = $this->client->CelsiusToFahrenheit(array(
'Celsius' => $celsius
)
);
if(is_soap_fault($result)) {
return $result;
}
return $result;
}
public function __getLastRequest() {
return $this->client->__getLastRequest();
}
}
try
{
$test = new TestClient( "http://www.w3schools.com/webservices/tempconvert.asmx?wsdl", $USER_AUTH_ARRAY);
echo "<pre>";
var_dump($test->CelsiusToFahrenheit( 0 ));
var_dump($test->__getLastRequest());
var_dump($test->CelsiusToFahrenheit( 20 ));
var_dump($test->__getLastRequest());
echo "</pre>";
}
catch (SoapFault $fault)
{
echo $fault->faultcode;
}
?>

Categories