summaryrefslogblamecommitdiffstats
path: root/inc/taskmanager.inc.php
blob: d9e890c6a30791654e71717de82c7a897e1960b5 (plain) (tree)
1
2
3
4
5
6
7
8






                                          
 



                                                                  
                                     
 

                                      

                                          
                                                                          

                                                                                                             


                                                               








                                                                                                                  
                                                                           

                             
                                          




                                                   

                                                                                
                                                
                                                     

                                     

                                    
                                               

                                                                                                            
                                                      

                                     


                              






                                                                                 
         




                                                            
                             
                                          
                                                 

                                                                                

                                      

                              
 






                                                                                  
                                                                   
         
                                                            







                                                                                                              
                              
                                                           
                                                      

                                                          
                                                                                                                  


                                             
                                       
                 
                          
                                             


                               





                                                                                                       

                                              
                                                                                           




                                                                                                                                             
 



















                                                                                                                   
 






                                                                                                                         
         




                                                            
                             
                                          
                                                  

                                                                        
 
           


                                                            









                                                                                                         

                                             









                                                                                                         
<?php

/**
 * Interface to the external task manager.
 */
class Taskmanager
{

	/**
	 * UDP socket used for communication with the task manager
	 * @var resource
	 */
	private static $sock = false;

	private static function init()
	{
		if (self::$sock !== false)
			return;
		self::$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
		socket_set_option(self::$sock, SOL_SOCKET, SO_RCVTIMEO, array('sec' => 0, 'usec' => 300000));
		socket_set_option(self::$sock, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 0, 'usec' => 200000));
		socket_connect(self::$sock, '127.0.0.1', 9215);
	}

	/**
	 * Start a task via the task manager.
	 *
	 * @param string $task name of task to start
	 * @param array $data data to pass to the task. the structure depends on the task.
	 * @param boolean $async if true, the function will not wait for the reply of the taskmanager, which means
	 *		the return value is just true (and you won't know if the task could acutally be started)
	 * @return array struct representing the task status, or result of submit, false on communication error
	 */
	public static function submit($task, $data = false, $async = false)
	{
		self::init();
		$seq = (string) mt_rand();
		if (empty($data)) {
			$data = '{}';
		} else {
			$data = json_encode($data);
		}
		$message = "$seq, $task, $data";
		$sent = socket_send(self::$sock, $message, strlen($message), 0);
		if ($sent != strlen($message)) {
			self::addErrorMessage(false);
			return false;
		}
		if ($async)
			return true;
		$reply = self::readReply($seq);
		if ($reply === false || !is_array($reply) || !isset($reply['id'])
				|| (isset($reply['statusCode']) && $reply['statusCode'] === NO_SUCH_TASK)) {
			self::addErrorMessage($reply);
			return false;
		}
		return $reply;
	}

	/**
	 * Query status of given task.
	 *
	 * @param mixed $task task id or task struct
	 * @return array status of task as array, or false on communication error
	 */
	public static function status($task)
	{
		if (is_array($task) && isset($task['id'])) {
			$task = $task['id'];
		}
		if (!is_string($task))
			return false;
		self::init();
		$seq = (string) mt_rand();
		$message = "$seq, status, $task";
		$sent = socket_send(self::$sock, $message, strlen($message), 0);
		$reply = self::readReply($seq);
		if (!is_array($reply))
			return false;
		return $reply;
	}

	/**
	 * Wait for the given task's completion.
	 *
	 * @param type $task task to wait for
	 * @param int $timeout maximum time in ms to wait for completion of task
	 * @return array result/status of task, or false if it couldn't be queried
	 */
	public static function waitComplete($task, $timeout = 1500)
	{
		if (is_array($task) && isset($task['id'])) {
			if ($task['statusCode'] !== TASK_PROCESSING && $task['statusCode'] !== TASK_WAITING) {
				self::release($task['id']);
				return $task;
			}
			$task = $task['id'];
		}
		if (!is_string($task))
			return false;
		$done = false;
		for ($i = 0; $i < ($timeout / 150); ++$i) {
			$status = self::status($task);
			if (!isset($status['statusCode']))
				break;
			if ($status['statusCode'] !== TASK_PROCESSING && $status['statusCode'] !== TASK_WAITING) {
				$done = true;
				break;
			}
			usleep(100000);
		}
		if ($done)
			self::release($task);
		return $status;
	}

	/**
	 * Check whether the given task can be considered failed.
	 *
	 * @param mixed $task task id or struct representing task
	 * @return boolean true if task failed, false if finished successfully or still waiting/running
	 */
	public static function isFailed($task)
	{
		if (!is_array($task) || !isset($task['statusCode']) || !isset($task['id']))
			return true;
		if ($task['statusCode'] !== TASK_WAITING && $task['statusCode'] !== TASK_PROCESSING && $task['statusCode'] !== TASK_FINISHED)
			return true;
		return false;
	}

	public static function addErrorMessage($task)
	{
		static $failure = false;
		if ($task === false) {
			if (!$failure) {
				Message::addError('taskmanager-error');
				$failure = true;
			}
			return;
		}
		if (!isset($task['statusCode'])) {
			Message::addError('taskmanager-format');
			return;
		}
		if (isset($task['data']['error'])) {
			Message::addError('task-error', $task['statusCode'] . ' (' . $task['data']['error'] . ')');
			return;
		}
		Message::addError('task-error', $task['statusCode']);
	}

	/**
	 * Release a given task from the task manager, so it won't keep the result anymore in case it's finished running.
	 *
	 * @param string $task task to release. can either be its id, or a struct representing the task, as returned
	 * by ::submit() or ::status()
	 */
	public static function release($task)
	{
		if (is_array($task) && isset($task['id'])) {
			$task = $task['id'];
		}
		if (!is_string($task))
			return;
		self::init();
		$seq = (string) mt_rand();
		$message = "$seq, release, $task";
		socket_send(self::$sock, $message, strlen($message), 0);
	}

	/**
	 * Read reply from socket for given sequence number.
	 *
	 * @param string $seq
	 * @return mixed the decoded json data for that message as an array, or null on error
	 */
	private static function readReply($seq)
	{
		$tries = 0;
		while (($bytes = socket_recvfrom(self::$sock, $buf, 90000, 0, $bla1, $bla2)) !== false) {
			$parts = explode(',', $buf, 2);
			if (count($parts) == 2 && $parts[0] == $seq) {
				return json_decode($parts[1], true);
			}
			if (++$tries > 10)
				return false;
		}
		//error_log(socket_strerror(socket_last_error(self::$sock)));
		return false;
	}

}

foreach (array('TASK_FINISHED', 'TASK_ERROR', 'TASK_WAITING', 'NO_SUCH_TASK', 'TASK_PROCESSING') as $i) {
	define($i, $i);
}