PHP ZF2 Mysql has gone away - php

Hi I have a php daemon that handle request from rabbitmq
After a day, it can no longer execute due to error MySQL has gone away.
PHP Warning: PDOStatement::execute(): MySQL server has gone away in /var/www/daemon/www/vendor/zendframework/zendframework/library/Zend/Db/Adapter/Driver/Pdo/Statement.php on line 239
PHP Warning: PDOStatement::execute(): Error reading result set\'s header in /var/www/daemon/www/vendor/zendframework/zendframework/library/Zend/Db/Adapter/Driver/Pdo/Statement.php on line 239
I didn't use doctrine, instead I send my \Zend\Db\Adapter\Adapter to a db wrapper class with below function.
public static function executeScalar($statement, $parameters, \Zend\Db\Adapter\Adapter $dbAdapter)
{
$dbResult = new DbResult();
if (! $statement) {
$dbResult->addError('No statement given');
return $dbResult;
}
$stmt = $dbAdapter->createStatement();
$stmt->prepare($statement);
foreach ($parameters as $key => &$param) {
$stmt->getResource()->bindParam($key + 1, $param[0], $param[1]);
}
try {
$result = $stmt->execute();
$dbResult->setResult($result);
} catch (\Zend\Db\Adapter\ExceptionInterface $e) {
$dbResult->addError('DB Error');
$message = $e->getPrevious() ? $e->getPrevious()->getMessage() : $e->getMessage();
$dbResult->addError($message);
} catch (\Zend\Db\Adapter\Exception $e) {
$dbResult->addError('DB Error');
$dbResult->addError($e->getMessage());
} catch (\PDOException $e) {
$dbResult->addError('DB Error');
$dbResult->addError($e->getMessage());
} catch (\Exception $e) {
$dbResult->addError('DB Error');
$dbResult->addError($e->getMessage());
}
$stmt->getResource()->closeCursor();
return $dbResult;
}
DbResult is my own db result wrapper class it mainly check whether it return empty, what's the error, how many rows, etc.
Here is my database.local.php configuration
return array(
'service_manager' => array(
'factories' => array(
'mysql' => function ($sm)
{
return new Zend\Db\Adapter\Adapter(array(
'driver' => 'PdoMysql',
'hostname' => 'localhost',
'database' => 'daemon',
'username' => 'daemon',
'password' => 'password',
'driver_options' => array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_EMULATE_PREPARES => true,
\PDO::MYSQL_ATTR_LOCAL_INFILE => true
)
));
},
)
)
)
So everytime I want to execute a sql I do this inside controller or any other class ( Just an example )
$service = $this->getServiceLocator();
$dbAdapter = $service->get('mysql');
$get = \Db\Database::executeScalar('SELECT * FROM mytable WHERE id <= ?', array(10), $dbAdapter);
It seems I cannot catch the warning, and is there a way to force reconnect or perhaps I just do a disconnect after each request ?
Will this works, to handle the error ?
On every new request I do this
$dbAdapter->getDriver()->getConnection()->connect();
At the end of request I do this
$dbAdapter->getDriver()->getConnection()->disconnect();

Yes, I check the persistent connection option, but I also not fond of it.
I find the problem, it cause by mysql server close idle connection after 'wait timeout'. when mysql closing the idle connection, PDO will not receive any event so the next time you initiate a query it will return Mysql has gone away error.
For http request this is acceptable since after the server response the request it will stop/exit php execution which close all connection to database.
For daemon/service this is not acceptable since most of the time it waiting for client request (idle). My solution is to close the connection everytime it finish handling client request. e.g :
while (true) {
//listen to rabbitmq queue
//...
//do something base on client request from rabbitmq queue
//...
//close the connection whether it use database or not
//connection will be reconnected when we call $service->get('mysql');
$service = $this->getServiceLocator();
$dbAdapter = $service->get('mysql');
$dbAdapter->getDriver()->getConnection()->disconnect();
}

You can create a persistent connection to your database but be warned that creating a persistent connection should not be the first solution to look for. Be sure to do some research on the subject before trying it. You can find some documentation here :
http://php.net/manual/en/pdo.connections.php#example-954
On the other hand, you should look for the queries sent so the reason of the gone away message is not caused by the mysql server recieving a packet too large (ex: inserting a large blob). Because if it is, the connection will still close unexpectedly.

Related

How to check if endpoint is working when using GuzzleHTTP

So I am working with guzzleHttp and I can get the responses that I am after and catch errors.
The only problem I am having is that if the base URI is wrong, the whole script fails... how can I do some sort of checking to make sure that the endpoint is actually up?
$client = new GuzzleHttp\Client(['base_uri' => $url]);
You might have many issues with your query, not only that the endpoint is down. Network interface on your server can go down right at the moment of query, DNS can go down, a route to the host might not be available, a connection timeout, etc.
So you definitely should be ready for many issues. I usually catch a general RequestException and do something (logging, app specific handling), plus catch specific exceptions if I should handle them differently.
Also, there are many existing patterns (and solutions) for error handling. For example, it's usual to retry a query is an endpoint is unavailable.
$stack = HandlerStack::create();
$stack->push(Middleware::retry(
function (
$retries,
RequestInterface $request,
ResponseInterface $response = null,
RequestException $exception = null
) {
// Don't retry if we have run out of retries.
if ($retries >= 5) {
return false;
}
$shouldRetry = false;
// Retry connection exceptions.
if ($exception instanceof ConnectException) {
$shouldRetry = true;
}
if ($response) {
// Retry on server errors.
if ($response->getStatusCode() >= 500) {
$shouldRetry = true;
}
}
// Log if we are retrying.
if ($shouldRetry) {
$this->logger->debug(
sprintf(
'Retrying %s %s %s/5, %s',
$request->getMethod(),
$request->getUri(),
$retries + 1,
$response ? 'status code: ' . $response->getStatusCode() :
$exception->getMessage()
)
);
}
return $shouldRetry;
}
));
$client = new Client([
'handler' => $stack,
'connect_timeout' => 60.0, // Seconds.
'timeout' => 1800.0, // Seconds.
]);

DBAL connect immediately

In PDO (And likewise DBAL) if there's a problem authenticating with the server it will throw an exception containing a trace including the database username and password.
Needless to say, this is a problem, and in PDO I would wrap it in a try block and re-throw the error without the stack trace. Problem solved.
However, DBAL doesn't actually initiate the connection until the first query is called, so it misses the try block completely and spews my database credentials the first chance it gets!
How can I force DBAL to connect immediately so I can catch any authentication errors?
\Doctrine\DBAL\Connection::connect()
Obvious in retrospect.
My database is ibm db2 so I use the odbc pdo connection with dbal. This is how I set up my connection, notice that the PDO is in a try catch block
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\QueryBuilder;
$connPdo = null;
try {
$connPdo = new PDO('odbc:'.SYSTEM_DSN_NAME, DB_USERNAME, DB_PASSWORD, array(
PDO::ATTR_PERSISTENT => TRUE,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
);
} catch (PDOException $e) {
echo $e->getMessage() . '</br>';
}
$config = new Configuration();
$connectionParams = array(
'user' => DB_USERNAME,
'password' => DB_PASSWORD,
'pdo' => $connPdo,
'driverClass' =>'Doctrine\DBAL\Driver\PDOIbm\Driver',
);
$connection = DriverManager::getConnection($connectionParams, $config);
$queryBuilder = $connection->createQueryBuilder();

PHP function shuts down on SoapClient::__doRequest connection timeout

I am attempting to write a background function that communicates with an external API to retrieve & update information on about 500,000 records on our server. This takes time to run. The problem I'm running into is this PHP fatal error from a function called inside a foreach loop, which shuts down the function entirely:
SoapClient::__doRequest(): connect() failed: Connection timed out in /path/to/function on line ABC
Here is a mock version of the function, with an indication of the line where the fatal error is being thrown, shutting down the function:
private function APIUpdateFunction($table,$record,$fields,$data) {
$soap = new SoapClient("https://www.endpoint.com/API/stuff.whatever");
$result = $soap->UpdateRecord(array(
"credentials" => API_CREDS,
"table" => $table,
"record" => $record,
"fields" => $fields,
"data" => $data
)); // THIS IS WHERE THE FATAL ERROR IS BEING THROWN
}
What I would like to do is create a workaround where IF the function shuts down as a result of this connection timeout, I would like to either:
A) Perform a new set of actions, such as restarting the function itself
or
B) Bypass the error entirely, and check in the next step for a response value. For example, if $result = NULL, the remaining items in this function would be bypassed and return a FALSE value.
What is the solution?
You can control the time your script takes to connect to the remote host/service and the time for how long the socket connection should wait for a response from the server.
The first option can be configured by specifying the timeout when you create the Soap Client:
$client = new SoapClient($wsdl, array("connection_timeout"=>15));
The second option can be configured using:
ini_set('default_socket_timeout', 180);
Now if you want to catch a timeout error, either during the host connection or the socket connection, you should catch a SoapFault exception, for example:
private function APIUpdateFunction($table,$record,$fields,$data)
{
try {
$soap = new SoapClient("https://www.endpoint.com/API/stuff.whatever");
$result = $soap->UpdateRecord(array(
"credentials" => API_CREDS,
"table" => $table,
"record" => $record,
"fields" => $fields,
"data" => $data
)); // THIS IS WHERE THE FATAL ERROR IS BEING THROWN
} catch (SoapFault $e) {
// There was an error with the Soap Client
// Do something here
}
}

PHP SOAP Client Error for none build-in functions

There is a wcf server and I am trying to connect it and send requests.
The code is :
$url = "http://serverip:8080/example.svc?wsdl";
$params = array(
'Username' => 'username',
'Password' => 'password'
);
$client = new SoapClient($url);
var_dump($client->__getTypes()); //it works
$result = $client->Login($params); //gives error
But everytime I am getting internal server error. I spend 5 days and searhed all the web and tried all different methods but I am always getting internal server error. Error is below :
protected 'message' => string 'The server was unable to process ...
private 'string' (Exception) => string '' (length=0) ...
If I use SOAP UI I can send and get data or if I call "$client->__getTypes()" from php server, I get types. But if I call implemented functions with PHP it doesn't work. Are there anyone to help me?
Thanks a lot,
Perhaps you should pass the parameters as object as in the following example
try {
$client = new SoapClient('http://serverip:8080/example.svc?wsdl');
$params = new stdClass();
$params->Username = 'Username';
$params->Password = 'Password';
$client->Login($params);
} catch (SoapFault $e) {
// error handling
}

unable to call ssh2_connect() callbacks

I am using the ssh2_connect() method to establish a connection with a remote server. A connection is establishing correctly if I am providing the right hostname and port number. When unable to connect due to wrong credentials, I am trying to call a callback-function but the way I try it isn't calling the callback after connection failure.
This i the code I tried:
$callbacks = array(
'ignore' => array($this, 'callbackSshDisconnect'),
'debug' => array($this, 'callbackSshDisconnect'),
'macerror' => array($this, 'callbackSshDisconnect'),
'disconnect' => array($this, 'callbackSshDisconnect'),
);
ssh2_connect($hostName,$port,array('hostkey', 'ssh-rsa'),$callbacks);
public function callbackSshDisconnect($reason, $message, $language) {
$this->log('disconnected');
$this->log($reason);die;
}
What I am doing wrong?
When ssh2_connect fails because of wrong host, port, and so on, it doesn't call any callbacks.
What is does, instead, is returning false
PHP documentation says :
Returns a resource on success, or FALSE on error.
$result = ssh2_connect($hostName,$port,array('hostkey' => 'ssh-rsa'),$callbacks);
if ($result === false)
exit("ssh2_connect failed");
Also, reading the doc, your
array('hostkey', 'ssh-rsa')
should be
array('hostkey' => 'ssh-rsa')
There's nothing you are doing wrong apart from the typo: array('hostkey', 'ssh-rsa') should be array('hostkey' => 'ssh-rsa'). The function ssh_connect() just returns false for a connection failure; callbacks simply are not initiated yet when the wrong credentials are being used.
There are solutions (e.g. as suggested by rubo77 below) but the one I find will give you most control, and allow you to do what you want (e.g. tracking MAC errors), is to use the phpseclib library (http://phpseclib.sourceforge.net/ssh/intro.html) for ssh connections and control. It gives very fine control of commands, and also includes logging.
It's not the simplest solution, but you can control as finely as if you are at the keyboard/terminal directly.
You have control over timeouts and can use callback on commands. But if you want better control, use read() and write() and you can monitor for disconnects or other issues. Check the documentation on logging with phpseclib: you can either log and parse the log, or call getLastError().
Log files will show 'Connection closed by server' on disconnects for example, but will also tell you if you're using an unsupported authentication mode on login etc.
Or for more, read the code: here, for example, are the disconnect reasons:
$this->disconnect_reasons = array(
1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
4 => 'NET_SSH2_DISCONNECT_RESERVED',
5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
);
Even if you would use working code, like this:
$callbacks = array(
'ignore' => 'callback',
'debug' => 'callback',
'macerror' => 'callback',
'disconnect' =>'callback',
);
$session = ssh2_connect($hostName, $port, array('hostkey' => 'ssh-rsa'), $callbacks);
if($session === false) {
print('Connection failed' . PHP_EOL);
exit(1);
}
if(!ssh2_auth_pubkey_file(
$session,
"user",
'/home/user/.ssh/id_rsa.pub',
'/home/user/.ssh/id_rsa'
)) {
print('Failed to authenticate session' . PHP_EOL);
exit(2);
}
// Check this great answer:
// http://stackoverflow.com/a/12094837/171318
$stream = ssh2_exec($session, "ls -al");
stream_set_blocking($stream, true);
$stream = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO);
echo stream_get_contents($stream);
function callback($reason, $message, $language) {
print("callback:" . PHP_EOL);
print(" " . $message . PHP_EOL);
print(" " . $reason . PHP_EOL);
}
the callbacks won't get called in a usual application. This is because they were called just under certain circumstances named in the manual page
The disconnect callback for example get's only called if a SSH2_MSG_DISCONNECT packet is received. Such a packet is not received unless the server closes the connection which will usually not happen.
When you are unable to connect due to wrong credentials then you won't get any callbacks anyway, ssh2_connect() just returns false in this case.
If you really want to catch the reason of the failure, you can use output buffer handler to extract the error message (assuming, you have error reporting set on):
set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) {
list($command,$errormessage)=explode(":",$errstr,2);
echo "Error ($errno):", $errormessage."<br><br>";
});
$connection = ssh2_connect($hostName,$port);
restore_error_handler();

Categories