<?php

// PHP class to restrict mysql queries to a certain time limit
// By: Jeremy Tharp 12-16-2011

// PLEASE NOTE:  It is highly inadvisable to use this restrictor for anything
// other than read-only queries as interrupting a write could have very
// unpleasant results.

class mysqlRestrictor {

	var $dbHost	= 'localhost';
	var $dbUser	= 'root';		// Must have ability to view processlist in information_schema and to kill processes
	var $dbPass	= '';
	var $dbName	= '';
	var $table	= '';
	var $timeLimit	= 2;			// In seconds

	function doConnect () {
		$db = mysql_connect ($this->dbHost, $this->dbUser, $this->dbPass);
		mysql_select_db ($this->dbName, $db);
		return $db;
	}

	function dbQuery ($query) {

		// Acquire a shared memory space to pass data back from the child process and initialize to NULL
		$shared = shm_attach (ftok(__FILE__, 'c'));
		shm_put_var($shared, 1, NULL);

		// Log that we have started the query
		if (!($db = $this->doConnect())) return null;
		mysql_query("INSERT INTO `".$this->table."` SET `status` = '0'", $db);
		$dbId = mysql_result(mysql_query("SELECT LAST_INSERT_ID() FROM `".$this->table."`", $db),0);
		mysql_close($db);

		// Log our start time
		$startTime = microtime(1);

		// Where's the forking function !?!!
		if (!function_exists('pcntl_fork'))	{ return mysql_query ($query, $db); }

		// Our db insert didn't work?
		else if ($dbId <= 0)			{ return mysql_query ($query, $db); }

		// Forking doesn't work
		else if (($pid = pcntl_fork()) == -1)	{ return mysql_query ($query, $db); }

		// We're in the child process, run the query
		else if ($pid == 0) {

			// Protection from zombification
			posix_setsid();

			// Connect and do our thing
			$db = $this->doConnect();
			$results = mysql_query($query, $db);
			$resultArray = array();
			while ($result = @mysql_fetch_assoc($results)) $resultArray[] = $result;
			shm_put_var($shared, 1, $resultArray);

			// If we're here, the query finished so we need to share it with the world
			mysql_query("UPDATE `".$this->table."` SET `status` = '1' WHERE `idx` = '".$dbId."';");

			// Tidy things up
			mysql_close();
			posix_kill(getmypid(),9);

		}

		// We're in the parent, count the seconds and beat the slow children silly
		else {

			// Get a fresh connection (so we don't conflict with the child)
			$db = $this->doConnect();

			// Loop for up to $timeLimit seconds
			while ((microtime(1) - $startTime) < $this->timeLimit) {
				$finished = mysql_result(mysql_query("SELECT `status` FROM `".$this->table."` WHERE `idx` = '".$dbId."';", $db),0);
				if ($finished == '1') break;
				usleep (250000);		// 250000 = 4 db queries a second
			}

			// Leave no trace except an auto-increment
			mysql_query("DELETE FROM `".$this->table."` WHERE `idx` = '".$dbId."'", $db);

			// Close our connection because we're a good boy
			mysql_close();

			// Make sure the child is dead before we leave the building
			posix_kill($pid,9);
			pcntl_waitpid($pid,$status);

		}

		// Get the value of the data (should be a serialized array)
		$results = shm_get_var($shared, 1);
		shm_remove ($shared);
		return $results;

	}

}

