@@ -280,31 +280,51 @@ public function dispatch() {
280280 * @param \PDOStatement $query
281281 * @return bool
282282 * @throws StorageBackendUnavailableExceptions
283- */
284- public function executeQuery (\PDOStatement $ query ) {
285- $ result = false ;
286- try {
287- $ result = $ query ->execute ();
288-
289- } catch (\Exception $ Exception ) {
290- $ errorNo = $ Exception ->errorInfo [1 ];
291- $ errorString = $ Exception ->errorInfo [2 ];
292- $ this ->Syslog ->error (sprintf ('[%s] %s ' , $ errorNo , $ errorString ));
293- $ this ->Syslog ->error ($ query ->queryString );
294- $ this ->Syslog ->error ("Run the worker in foreground mode to see the full query: https://statusengine.org/worker/#debugging " );
295-
296- // This function has no return - so no log file -.-
297- // If Statusengine is running via systemd systemd will write the messages to syslog
298- if ($ this ->isDumpOfMysqlQueryParametersEnabled ) {
299- $ query ->debugDumpParams ();
300- }
301-
302- if ($ errorString == 'MySQL server has gone away ' ) {
303- $ this ->reconnect ();
304- throw new StorageBackendUnavailableExceptions ($ errorString );
283+ * @package string $caller
284+ */
285+ public function executeQuery (\PDOStatement $ query , $ caller = 'Unknown ' ) {
286+ // https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html
287+ // https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html
288+ // The deadlock logic is ported from Statusengine 2
289+ // https://github.com/nook24/statusengine/blame/b5c86f0e02fd69a7045eb652f49dad20ee4d2b67/cakephp/app/src/BulkRepository.php#L185
290+ $ retries = 10 ;
291+ for ($ i = 1 ; $ i < $ retries ; $ i ++) {
292+ try {
293+ return $ query ->execute ();
294+
295+ } catch (\Exception $ Exception ) {
296+ $ sqlstateErrorCode = $ Exception ->errorInfo [0 ]; // SQLSTATE error code (a five characters alphanumeric identifier defined in the ANSI SQL standard).
297+ $ errorNo = $ Exception ->errorInfo [1 ]; // Driver-specific error code.
298+ $ errorString = $ Exception ->errorInfo [2 ]; // Driver-specific error message.
299+ $ this ->Syslog ->error (sprintf ('[%s] %s ' , $ errorNo , $ errorString ));
300+
301+ if ($ i <= $ retries && $ sqlstateErrorCode == 40001 && $ errorNo == 1213 ) {
302+ // This is a InnoDB deadlock - retry
303+ $ sleep = 50000 + rand (0 , 450000 );
304+ $ this ->Syslog ->info ('Encountered MySQL Deadlock during transaction on ' . $ caller . '. Retry transaction in ' . floor ($ sleep / 1000 ) . 'ms (try ' . ($ i ) . '/ ' . $ retries . ') ' );
305+ usleep ($ sleep );
306+ } else if ($ sqlstateErrorCode == 40001 && $ errorNo == 1213 ) {
307+ // too many deadlocks
308+ $ this ->Syslog ->info ('Couldn \'t solve deadlock for ' . $ caller . '. Ignore for now to prevent crash: Exception: ' . $ Exception ->getMessage ());
309+ } else {
310+ // Any other error
311+ $ this ->Syslog ->error ($ query ->queryString );
312+ $ this ->Syslog ->error ("Run the worker in foreground mode to see the full query: https://statusengine.org/worker/#debugging " );
313+
314+ // This function has no return - so no log file -.-
315+ // If Statusengine is running via systemd systemd will write the messages to syslog
316+ if ($ this ->isDumpOfMysqlQueryParametersEnabled ) {
317+ $ query ->debugDumpParams ();
318+ }
319+
320+ if ($ errorString == 'MySQL server has gone away ' ) {
321+ $ this ->reconnect ();
322+ throw new StorageBackendUnavailableExceptions ($ errorString );
323+ }
324+ }
305325 }
306326 }
307- return $ result ;
327+ return false ;
308328 }
309329
310330 /**
0 commit comments