function enqueue($fid, $prioridad = 10) { // Carga biblioteca $this->_inicializa_pheanstalk(); // Guardar en BBDD $result = $this->store($fid, self::PENDING, ''); if ($result === FALSE) { return FALSE; } // Encolar en beanstalkd $host = $this->config->item('beanstalkd_host'); $port = $this->config->item('beanstalkd_port'); $tube = $this->config->item('beanstalkd_tube'); $exito = FALSE; try { $pheanstalk = new Pheanstalk($host, $port); $pheanstalk->useTube($tube)->put("SCAN " . $fid, $prioridad); $exito = TRUE; } catch (Exception $e) { // TODO : más detalle log_message('error', 'Error encolando trabajo en beanstalkd'); $this->store($fid, self::ERROR, 'El fichero no se pudo encolar para su revisión'); /* En este caso, no es mala idea que el fichero quede * en la base de datos marcado con ERROR, pero que la ejecución * siga */ } if ($exito === TRUE) { log_message('info', 'Fichero ' . $fid . ' encolado para ser escaneado'); } }
/** * @throws Exception\WorkerException */ public function testWorkerRuns() { $testWorkerRuns = $this; $tube = 'worker_tube_' . rand(53, 504); $data = 'worker_value_' . rand(95, 3000); $pheanstalk = new Pheanstalk(self::SERVER_HOST, self::SERVER_PORT); $worker = new Worker(self::SERVER_HOST, self::SERVER_PORT); $job = $pheanstalk->useTube($tube)->put($data); $worker->register($tube, function (Job $job) use($testWorkerRuns, $data) { $testWorkerRuns->assertEquals($data, $job->getData()); }); $processedJob = $worker->processOne(0); $stats = $pheanstalk->statsTube($tube); $this->assertEquals($stats['total-jobs'], 1); }
private function _createPheanstalk() { $pheanstalk = new Pheanstalk(self::SERVER_HOST); $tube = preg_replace('#[^a-z]#', '', strtolower(__CLASS__)); $pheanstalk->useTube($tube)->watch($tube)->ignore('default'); try { while ($pheanstalk->delete($pheanstalk->peekDelayed())) { } } catch (Pheanstalk_Exception_ServerException $e) { } try { while ($pheanstalk->delete($pheanstalk->peekReady())) { } } catch (Pheanstalk_Exception_ServerException $e) { } return $pheanstalk; }
/** * dispatchWork * * @param string $function * @param array $parameters * @param object $socket */ protected function dispatchWork($f,$p) { $this->Log->debug(__METHOD__); // If this work is to be done locally set it up right here if('local'==$this->work['method']) { // dispatch the API call include_once($_SERVER['SCRIPT_FILENAME']); $this->Log->debug('include_once('.$_SERVER['SCRIPT_FILENAME'].')'); // The expected class name $class_name = ANYAPI_CALL_CLASS; if(!class_exists($class_name)) { $this->Log->error("Code 531: Unable to locate the expected class named '".$class_name."' in ".$_SERVER['SCRIPT_FILENAME']); $response['error'] = array('code'=>'531','message'=>'Internal system error'); $this->out($response); // terminates } // Try and instantiate the class try { $anyapi = new $class_name; } catch (Exception $e) { $this->Log->error("Code 540: Unable instantiate class named '".$class_name."' got exception ".$e); $response['error'] = array('code'=>'540','message'=>'Internal system error'); $this->out($response); // terminates } // There must be a better way to do this - uck!! if(0==count($p)) { return $anyapi->$f(); } elseif(1==count($p)) { return $anyapi->$f($p[0]); } elseif(2==count($p)) { return $anyapi->$f($p[0],$p[1]); } elseif(3==count($p)) { return $anyapi->$f($p[0],$p[1],$p[2]); } elseif(4==count($p)) { return $anyapi->$f($p[0],$p[1],$p[2],$p[3]); } elseif(5==count($p)) { return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4]); } elseif(6==count($p)) { return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4],$p[5]); } elseif(7==count($p)) { return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4],$p[5],$p[6]); } elseif(8==count($p)) { return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4],$p[5],$p[6],$p[7]); } elseif(9==count($p)) { return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4],$p[5],$p[6],$p[7],$p[8]); } elseif(10==count($p)){ return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4],$p[5],$p[6],$p[7],$p[8],$p[9]); } elseif(11==count($p)){ return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4],$p[5],$p[6],$p[7],$p[8],$p[9],$p[10]); } elseif(12==count($p)){ return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4],$p[5],$p[6],$p[7],$p[8],$p[9],$p[10],$p[11]); } elseif(13==count($p)){ return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4],$p[5],$p[6],$p[7],$p[8],$p[9],$p[10],$p[11],$p[12]); } elseif(14==count($p)){ return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4],$p[5],$p[6],$p[7],$p[8],$p[9],$p[10],$p[11],$p[12],$p[13]); } elseif(15==count($p)){ return $anyapi->$f($p[0],$p[1],$p[2],$p[3],$p[4],$p[5],$p[6],$p[7],$p[8],$p[9],$p[10],$p[11],$p[12],$p[13],$p[14]); } $this->Log->error("Code 563: AnyApi only supports 15x input parameters for an action - got an elegant fix?"); $response['error'] = array('code'=>'563','message'=>'Internal system error'); return $response; } // Check we are setup for beanstalkd since it's the only type supported at the moment if('beanstalkd' != $this->work['queue']['type']) { $this->Log->error("Code 565: AnyApi only supports work[queue][type] = 'beanstalkd'"); $response['error'] = array('code'=>'565','message'=>'Internal system error'); return $response; } $socket = null; // Only create a return data socket if the worker will not be doing an external callback if(!isset($this->request['return_options']['callback']['url']) && !isset($this->request['return_options']['callback']['email'])) { $this->Log->debug("Creating an internet tcp stream socket for work return data"); $socket = socket_create(AF_INET,SOCK_STREAM,getprotobyname('tcp')); // Make sure we created the socket if(!$socket) { $this->Log->error("Code 490: Unable to create work queue return data socket!"); $response['error'] = array('code'=>'490','message'=>'Internal system error'); $this->out($response); // terminates } // Bind the socket to something we can use $return_token = false; while(!$return_token) { $port_delta = $this->work['queue']['callback_socket']['max_port'] - $this->work['queue']['callback_socket']['min_port']; $port = $this->work['queue']['callback_socket']['min_port'] + rand(0,$port_delta); if(@socket_bind($socket,$this->work['queue']['callback_socket']['address'],$port)) { $this->Log->debug('Bound socket to '.$this->work['queue']['callback_socket']['address'].':'.$port.' ready for work queue return data'); $return_token = md5(mt_rand(0,10000000).microtime(true)); } } $this->request['return_options']['callback']['socket'] = array( 'address' =>$this->work['queue']['callback_socket']['address'], 'port' => $port, 'token' => $return_token, 'timeout' => $this->work['queue']['callback_socket']['timeout'] ); } // Beanstalk! include_once(dirname(__FILE__).'/pheanstalk/pheanstalk_init.php'); $Pheanstalk = new Pheanstalk($this->work['queue']['host'],$this->work['queue']['port'],$this->work['queue']['connect_timeout']); $job_id = null; try { $job_id = $Pheanstalk ->useTube(ANYAPI_CALL_CLASS) // use the class name for the tube name ->put(gzcompress(serialize(array($f,$p,$this->request['return_options'])),9)); } catch (Exception $e) { $this->Log->error("Code 589: Unable to place work onto beanstalkd queue at ".$this->work['queue']['host'].':'.$this->work['queue']['port']); $response['error'] = array('code'=>'589','message'=>'Internal system error'); return $response; } $this->Log->debug("Work placed on beanstalkd queue with job_id:".$job_id); // Wait for callback response from the worker into the socket here socket_listen($socket); $this->Log->debug("Socket listening for response data for job_id:".$job_id); socket_set_nonblock($socket); $this->Log->debug("Socket set to non-blocking mode for job_id:".$job_id); // Wait until timeout for the callback socket response $timeout_timestamp = time() + $this->work['queue']['callback_socket']['timeout']; $usleep_interval = 100; // 1/10000th of a second $connection = false; while(!$connection) { $connection = @socket_accept($socket); usleep($usleep_interval); $usleep_interval = $usleep_interval*1.1; // gradual scaling back of $usleep_interval if(time() > $timeout_timestamp) { $this->Log->error('Code 456: Timed out after while for response data from job_id: '.$job_id); $response['error'] = array('code'=>'456','message'=>'Internal system error'); return $response; } } // Slurp up the data in a buffered manner $data = null; do { @socket_recv($connection, $buffer,1024,MSG_DONTWAIT); // Protect against resource temporarily unavailable problems if(11 == socket_last_error($connection)) { socket_clear_error($connection); $buffer = 'CONTINUE'; } else { $data .= $buffer; } } while (!empty($buffer)); // Uncompress the data returned through the socket $data = @gzuncompress($data); // Strip apart data and token componets $token = substr($data,(strlen($data)-strlen($this->request['return_options']['callback']['socket']['token'])),strlen($this->request['return_options']['callback']['socket']['token'])); $data = substr($data,0,strlen($data)-strlen($this->request['return_options']['callback']['socket']['token'])-1 ); // Make sure the token matches the expected value if($token != $this->request['return_options']['callback']['socket']['token']) { $this->Log->error('Code 472: Invalid return data token for job_id: '.$job_id); $response['error'] = array('code'=>'472','message'=>'Internal system error'); return $response; } // Detect if this is raw binary data or not as indicated by a BIN\0 at the front of the string if("BIN\0" == substr($data,0,4)) { return substr($data,4); } return json_decode($data,true); }
/** * Put the error on a beanstalk queue so a separate process can * send it off to Airbrake via HTTP. Requires the pheanstalk client. * Config: * $BEANSTALK_SERVERS[] = array( * 'host' => 'example.com', * 'port' => '11300' * ); * @author Aaron Parecki */ public function beanstalkRequest($url, $headers, $body) { global $BEANSTALK_SERVERS; require_once 'pheanstalk/pheanstalk_init.php'; $k = array_rand($BEANSTALK_SERVERS); $pheanstalk = new Pheanstalk($BEANSTALK_SERVERS[$k]['host'], $BEANSTALK_SERVERS[$k]['port']); $pheanstalk->useTube('airbrake'); $pheanstalk->put(json_encode(array('url' => $url, 'headers' => $headers, 'body' => $body))); }