public function registerEvents() { $job =& $this->job; Event::listen(Event::JOB_PERFORM, function ($event, $_job) use(&$job) { $job = $_job; }); }
/** * Enqueue a job for execution at a given timestamp. * * Identical to Resque::enqueue, however the first argument is a timestamp * (either UNIX timestamp in integer format or an instance of the DateTime * class in PHP). * * @param DateTime|int $at Instance of PHP DateTime object or int of UNIX timestamp. * @param string $queue The name of the queue to place the job in. * @param string $class The name of the class that contains the code to execute the job. * @param array $args Any optional arguments that should be passed when the job is executed. */ public static function enqueueAt($at, $queue, $class, $args = array()) { self::validateJob($class, $queue); $job = self::jobToHash($queue, $class, $args); self::delayedPush($at, $job); Event::trigger('afterSchedule', array('at' => $at, 'queue' => $queue, 'class' => $class, 'args' => $args)); }
/** * Schedule all of the delayed jobs for a given timestamp. * * Searches for all items for a given timestamp, pulls them off the list of * delayed jobs and pushes them across to Resque. * * @param DateTime|int $timestamp Search for any items up to this timestamp to schedule. */ public function enqueueDelayedItemsForTimestamp($timestamp) { $item = null; while ($item = Scheduler::nextItemForTimestamp($timestamp)) { $this->log('queueing ' . $item['class'] . ' in ' . $item['queue'] . ' [delayed]'); if (!empty($item['args'])) { $item['args'] = reset($item['args']); } Event::trigger('beforeDelayedEnqueue', array('queue' => $item['queue'], 'class' => $item['class'], 'args' => $item['args'])); Resque::enqueue($item['queue'], $item['class'], $item['args']); } }
/** * Mark the current job as having failed * * @param \Exception $e */ public function fail(\Exception $e) { $this->stopped(); $this->setStatus(Job::STATUS_FAILED, $e); // For the failed jobs we store a lot more data for debugging $packet = $this->getPacket(); $failed_payload = array_merge(json_decode($this->payload, true), array('worker' => $packet['worker'], 'started' => $packet['started'], 'finished' => $packet['finished'], 'output' => $packet['output'], 'exception' => (array) json_decode($packet['exception'], true))); $this->redis->zadd(Queue::redisKey($this->queue, 'failed'), time(), json_encode($failed_payload)); Stats::incr('failed', 1); Stats::incr('failed', 1, Queue::redisKey($this->queue, 'stats')); Event::fire(Event::JOB_FAILURE, array($this, $e)); }
/** * Mark the current job as having failed. * * @param $exception */ public function fail($exception) { Event::trigger('onFailure', array('exception' => $exception, 'job' => $this)); $this->updateStatus(Status::STATUS_FAILED); Failure::create($this->payload, $exception, $this->worker, $this->queue); Stat::incr('failed'); Stat::incr('failed:' . $this->worker); }
/** * Look for any workers which should be running on this server and if * they're not, remove them from Redis. * * This is a form of garbage collection to handle cases where the * server may have been killed and the workers did not die gracefully * and therefore leave state information in Redis. */ public function cleanup() { $workers = self::allWorkers(); $hosts = $this->redis->smembers(Host::redisKey()); $cleaned = array(); foreach ($workers as $worker) { list($host, $pid) = explode(':', (string) $worker, 2); if ($host != (string) $this->host and in_array($host, $hosts) or $host == (string) $this->host and posix_kill((int) $pid, 0)) { continue; } $this->log('Pruning dead worker: ' . $worker, Logger::DEBUG); $worker->unregister(); $cleaned[] = (string) $worker; } $workerIds = array_map(function ($w) { return (string) $w; }, $workers); $keys = (array) $this->redis->keys('worker:' . $this->host . ':*'); foreach ($keys as $key) { $key = $this->redis->removeNamespace($key); $id = substr($key, strlen('worker:')); if (!in_array($id, $workerIds)) { if ($this->redis->ttl($key) < 0) { $this->log('Expiring worker data: ' . $key, Logger::DEBUG); $this->redis->expire($key, \Resque::getConfig('default.expiry_time', \Resque::DEFAULT_EXPIRY_TIME)); } } } Event::fire(Event::WORKER_CLEANUP, array($this, $cleaned)); return $cleaned; }
/** * Initialises the command just after the input has been validated. * * This is mainly useful when a lot of commands extends one main command * where some things need to be initialised based on the input arguments and options. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * @return void */ protected function initialize(InputInterface $input, OutputInterface $output) { $this->parseConfig($input->getOptions(), $this->getNativeDefinition()->getOptionDefaults()); $config = $this->getConfig(); // Configure Redis Resque\Redis::setConfig(array('scheme' => $config['scheme'], 'host' => $config['host'], 'port' => $config['port'], 'namespace' => $config['namespace'], 'password' => $config['password'])); // Set the verbosity if (array_key_exists('verbose', $config)) { if (!$input->getOption('verbose') and !$input->getOption('quiet') and is_int($config['verbose'])) { $output->setVerbosity($config['verbose']); } else { $this->config['verbose'] = $output->getVerbosity(); } } // Set the monolog loggers, it's possible to speficfy multiple handlers $logs = array_key_exists('log', $config) ? array_unique($config['log']) : array(); empty($logs) and $logs[] = 'console'; $handlerConnector = new Resque\Logger\Handler\Connector($this, $input, $output); $handlers = array(); foreach ($logs as $log) { $handlers[] = $handlerConnector->resolve($log); } $this->logger = $logger = new Resque\Logger($handlers); // Unset some variables so as not to pass to include file unset($logs, $handlerConnector, $handlers); // Include file? if (array_key_exists('include', $config) and strlen($include = $config['include'])) { if (!($includeFile = realpath(dirname($include) . '/' . basename($include))) or !is_readable($includeFile) or !is_file($includeFile) or substr($includeFile, -4) !== '.php') { throw new \InvalidArgumentException('The include file "' . $include . '" is not a readable php file.'); } try { require_once $includeFile; } catch (\Exception $e) { throw new \RuntimeException('The include file "' . $include . '" threw an exception: "' . $e->getMessage() . '" on line ' . $e->getLine()); } } // This outputs all the events that are fired, useful for learning // about when events are fired in the command flow if (array_key_exists('events', $config) and $config['events'] === true) { Resque\Event::listen('*', function ($event) use($output) { $data = array_map(function ($d) { $d instanceof \Exception and $d = '"' . $d->getMessage() . '"'; is_array($d) and $d = '[' . implode(',', $d) . ']'; return (string) $d; }, array_slice(func_get_args(), 1)); $output->writeln('<comment>-> event:' . Resque\Event::eventName($event) . '(' . implode(',', $data) . ')</comment>'); }); } }
<?php use Resque\Event; use Resque\Logger; // Test job class class TestJob { public function perform($args) { // Don't do anything } } // Lets record the forking time Event::listen(array(Event::WORKER_FORK, Event::WORKER_FORK_CHILD), function ($event, $job) use($logger) { static $start = 0; if ($event === Event::WORKER_FORK_CHILD) { $exec = microtime(true) - $start; $logger->log('Forking process took ' . round($exec * 1000, 2) . 'ms', Logger::DEBUG); } else { $start = microtime(true); } }); // When the job is about to be run, queue another one Event::listen(Event::JOB_PERFORM, function ($event, $job) use($logger) { Resque::push('TestJob'); }); // Add a few jobs to the default queue for ($i = 0; $i < 10; $i++) { Resque::push('TestJob'); }
/** * Perform necessary actions to start a worker. */ private function startup() { $this->registerSigHandlers(); $this->pruneDeadWorkers(); Event::trigger('beforeFirstFork', $this); $this->registerWorker(); }