diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a725465 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 1284d21..cdb4478 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,12 @@ language: php php: - - 5.2 - 5.3 - 5.4 +env: + - REDIS_STANDALONE=0 + - REDIS_STANDALONE=1 +before_script: + - sh -c "if [ $REDIS_STANDALONE -eq 0 ]; then wget https://github.com/nicolasff/phpredis/archive/2.2.2.zip -O php-redis.zip && unzip php-redis.zip; fi" + - sh -c "if [ $REDIS_STANDALONE -eq 0 ]; then cd phpredis-2.2.2/ && phpize && ./configure && make && make install; fi" + - sh -c "if [ $REDIS_STANDALONE -eq 0 ]; then echo \"extension=redis.so\" >> `php --ini | grep \"Loaded Configuration\" | sed -e \"s|.*:\s*||\"`; fi" + - composer install \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f2d66b..f6f1196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,35 @@ -## 1.2 (Unreleased) ## +## 1.3 (2013-??-??) - Current Master ## + +**Note:** This release introduces backwards incompatible changes with all previous versions of php-resque. Please see below for details. + +### Redisent (Redis Library) Replaced with Credis + +Redisent has always been the Redis backend for php-resque because of its lightweight nature. Unfortunately, Redisent is largely unmaintained. + +[Credis](http://example.com/) is a fork of Redisent, which among other improvements will automatically use the [phpredis](https://github.com/nicolasff/phpredis) native PHP extension if it is available. (you want this for speed, trust me) + +php-resque now utilizes Credis for all Redis based operations. Credis automatically required and installed as a Composer dependency. + +### Composer Support + +Composer support has been improved and is now the recommended method for including php-resque in your project. Details on Composer support can be found in the Getting Started section of the readme. + +### Other Improvements/Changes + +* **COMPATIBILITY BREAKING**: The bundled worker manager `resque.php` has been moved to `bin/resque`, and is available as `vendor/bin/resque` when php-resque is installed as a Composer package. + +* Restructure tests and test bootstrapping. Autoload tests via Composer (install test dependencies with `composer install --dev`) + +* Add `SETEX` to list of commands which supply a key as the first argument in Redisent (danhunsaker) + +* Fix an issue where a lost connection to Redis could cause an infinite loop (atorres757) + +* Add a helper method to `Resque_Redis` to remove the namespace applied to Redis keys (tonypiper) + + +## 1.2 (2012-10-13) ## + +**Note:** This release is largely backwards compatible with php-resque 1.1. The next release will introduce backwards incompatible changes (moving from Redisent to Credis), and will drop compatibility with PHP 5.2. * Allow alternate redis database to be selected when calling setBackend by supplying a second argument (patrickbajao) * Use `require_once` when including php-resque after the app has been included in the sample resque.php to prevent include conflicts (andrewjshults) @@ -10,7 +41,17 @@ * Fix lost jobs when there is more than one worker process started by the same parent process (salimane) * Move include for resque before APP_INCLUDE is loaded in, so that way resque is available for the app * Avoid working with dirty worker IDs (salimane) - +* Allow UNIX socket to be passed to Resque when connecting to Redis (pedroarnal) +* Fix typographical errors in PHP docblocks (chaitanyakuber) +* Set the queue name on job instances when jobs are executed (chaitanyakuber) +* Fix and add tests for Resque_Event::stopListening (ebernhardson) +* Documentation cleanup (maetl) +* Pass queue name to afterEvent callback +* Only declare RedisException if it doesn't already exist (Matt Heath) +* Add support for Composer +* Fix missing and incorrect paths for Resque and Resque_Job_Status classes in demo (jjfrey) +* Disable autoload for the RedisException class_exists call (scragg0x) +* General tidy up of comments and files/folders ## 1.1 (2011-03-27) ## diff --git a/LICENSE b/LICENSE index 6513591..a796ebf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -(c) 2010 Chris Boulton +(c) Chris Boulton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 2f942ab..8e11988 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,34 @@ pre and post jobs ## Requirements ## -* PHP 5.2+ +* PHP 5.3+ * Redis 2.2+ +* Optional but Recommended: Composer + +## Getting Started ## + +The easiest way to work with php-resque is when it's installed as a +Composer package inside your project. Composer isn't strictly +required, but makes life a lot easier. + +If you're not familiar with Composer, please see . + +1. Add php-resque to your application's composer.json. + + { + ... + "require": { + "chrisboulton/php-resque": "1.2.x" + }, + ... + } + +2. Run `composer install`. + +3. If you haven't already, add the Composer autoload to your project's + initialization file. (example) + + require 'vendor/autoload.php'; ## Jobs ## @@ -46,8 +72,6 @@ pre and post jobs Jobs are queued as follows: - require_once 'lib/Resque.php'; - // Required if redis is located elsewhere Resque::setBackend('localhost:6379'); @@ -87,12 +111,12 @@ The `tearDown` method if defined, will be called after the job finishes. { // ... Set up environment for this job } - + public function perform() { // .. Run job } - + public function tearDown() { // ... Remove environment for this job @@ -136,8 +160,9 @@ class. Workers work in the exact same way as the Ruby workers. For complete documentation on workers, see the original documentation. -A basic "up-and-running" resque.php file is included that sets up a -running worker environment is included in the root directory. +A basic "up-and-running" `bin/resque` file is included that sets up a +running worker environment is included. (`vendor/bin/resque` when installed +via Composer) The exception to the similarities with the Ruby version of resque is how a worker is initially setup. To work under all environments, @@ -146,13 +171,17 @@ not having a single environment such as with Ruby, the PHP port makes To start a worker, it's very similar to the Ruby version: - $ QUEUE=file_serve php resque.php + $ QUEUE=file_serve php bin/resque It's your responsibility to tell the worker which file to include to get your application underway. You do so by setting the `APP_INCLUDE` environment variable: - $ QUEUE=file_serve APP_INCLUDE=../application/init.php php resque.php + $ QUEUE=file_serve APP_INCLUDE=../application/init.php php bin/resque + +*Pro tip: Using Composer? More than likely, you don't need to worry about +`APP_INCLUDE`, because hopefully Composer is responsible for autoloading +your application too!* Getting your application underway also includes telling the worker your job classes, by means of either an autoloader or including them. @@ -163,8 +192,8 @@ The port supports the same environment variables for logging to STDOUT. Setting `VERBOSE` will print basic debugging information and `VVERBOSE` will print detailed information. - $ VERBOSE QUEUE=file_serve php resque.php - $ VVERBOSE QUEUE=file_serve php resque.php + $ VERBOSE QUEUE=file_serve bin/resque + $ VVERBOSE QUEUE=file_serve bin/resque ### Priorities and Queue Lists ### @@ -175,7 +204,7 @@ checked in. As per the original example: - $ QUEUE=file_serve,warm_cache php resque.php + $ QUEUE=file_serve,warm_cache bin/resque The `file_serve` queue will always be checked for new jobs on each iteration before the `warm_cache` queue is checked. @@ -185,14 +214,14 @@ iteration before the `warm_cache` queue is checked. All queues are supported in the same manner and processed in alphabetical order: - $ QUEUE=* php resque.php + $ QUEUE=* bin/resque ### Running Multiple Workers ### Multiple workers ca be launched and automatically worked by supplying the `COUNT` environment variable: - $ COUNT=5 php resque.php + $ COUNT=5 bin/resque ### Forking ### @@ -257,7 +286,7 @@ It is up to your application to register event listeners. When enqueuing events in your application, it should be as easy as making sure php-resque is loaded and calling `Resque_Event::listen`. -When running workers, if you run workers via the default `resque.php` script, +When running workers, if you run workers via the default `bin/resque` script, your `APP_INCLUDE` script should initialize and register any listeners required for operation. If you have rolled your own worker manager, then it is again your responsibility to register listeners. @@ -329,3 +358,16 @@ Called after a job has been queued using the `Resque::enqueue` method. Arguments * KevBurnsJr * jmathai * dceballos +* patrickbajao +* andrewjshults +* warezthebeef +* d11wtq +* hlegius +* salimane +* humancopy +* pedroarnal +* chaitanyakuber +* maetl +* Matt Heath +* jjfrey +* scragg0x diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 61ea867..0000000 --- a/TODO.md +++ /dev/null @@ -1,8 +0,0 @@ -* Write tests for: - * `Resque_Failure` - * `Resque_Failure_Redis` -* Change to preforking worker model -* Clean up /bin and /demo -* Add a way to store arbitrary text in job statuses (for things like progress -indicators) -* Write plugin for Ruby resque that calls setUp and tearDown methods \ No newline at end of file diff --git a/bin/resque b/bin/resque index 1a24852..78befad 100644 --- a/bin/resque +++ b/bin/resque @@ -1 +1,106 @@ -#!/bin/sh +#!/usr/bin/env php + 1) { + $count = $COUNT; +} + +if($count > 1) { + for($i = 0; $i < $count; ++$i) { + $pid = Resque::fork(); + if($pid == -1) { + die("Could not fork worker ".$i."\n"); + } + // Child, start the worker + else if(!$pid) { + $queues = explode(',', $QUEUE); + $worker = new Resque_Worker($queues); + $worker->logLevel = $logLevel; + fwrite(STDOUT, '*** Starting worker '.$worker."\n"); + $worker->work($interval); + break; + } + } +} +// Start a single worker +else { + $queues = explode(',', $QUEUE); + $worker = new Resque_Worker($queues); + $worker->logLevel = $logLevel; + + $PIDFILE = getenv('PIDFILE'); + if ($PIDFILE) { + file_put_contents($PIDFILE, getmypid()) or + die('Could not write PID information to ' . $PIDFILE); + } + + fwrite(STDOUT, '*** Starting worker '.$worker."\n"); + $worker->work($interval); +} +?> diff --git a/composer.json b/composer.json index 594f13f..32f92ad 100644 --- a/composer.json +++ b/composer.json @@ -11,9 +11,26 @@ "email": "chris@bigcommerce.com" } ], + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/chrisboulton/credis" + } + ], "require": { - "php": ">=5.3.0" + "php": ">=5.3.0", + "colinmollenhour/credis": "dev-master" }, + "suggest": { + "ext-proctitle": "Allows php-resque to rename the title of UNIX processes to show the status of a worker.", + "ext-redis": "Native PHP extension for Redis connectivity. Credis will automatically utilize when available." + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "bin": [ + "bin/resque" + ], "autoload": { "psr-0": { "Resque": "lib" diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..877c243 --- /dev/null +++ b/composer.lock @@ -0,0 +1,53 @@ +{ + "hash": "d37909ad0ffc11ed4d1e67dcaabe00b2", + "packages": [ + { + "name": "colinmollenhour/credis", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/chrisboulton/credis", + "reference": "62c73dd16e08069e3fd8f224cb4a5ddd73db8095" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chrisboulton/credis/zipball/62c73dd16e08069e3fd8f224cb4a5ddd73db8095", + "reference": "62c73dd16e08069e3fd8f224cb4a5ddd73db8095", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2013-01-12 10:15:31", + "type": "library", + "autoload": { + "classmap": [ + "Client.php", + "Cluster.php" + ] + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Colin Mollenhour", + "email": "colin@mollenhour.com" + } + ], + "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.", + "homepage": "https://github.com/colinmollenhour/credis", + "support": { + "source": "https://github.com/chrisboulton/credis/tree/master" + } + } + ], + "packages-dev": null, + "aliases": [ + + ], + "minimum-stability": "stable", + "stability-flags": { + "colinmollenhour/credis": 20 + } +} diff --git a/demo/check_status.php b/demo/check_status.php index c195911..061a83a 100644 --- a/demo/check_status.php +++ b/demo/check_status.php @@ -3,8 +3,8 @@ if(empty($argv[1])) { die('Specify the ID of a job to monitor the status of.'); } -require '../lib/Resque/Job/Status.php'; -require '../lib/Resque.php'; +require __DIR__ . '/init.php'; + date_default_timezone_set('GMT'); Resque::setBackend('127.0.0.1:6379'); diff --git a/demo/init.php b/demo/init.php new file mode 100644 index 0000000..9078bcd --- /dev/null +++ b/demo/init.php @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/extras/resque.monit b/extras/resque.monit index 654815d..b611f8f 100644 --- a/extras/resque.monit +++ b/extras/resque.monit @@ -9,7 +9,7 @@ check process resque_worker_[QUEUE] with pidfile /var/run/resque/worker_[QUEUE].pid - start program = "/bin/sh -c 'APP_INCLUDE=[APP_INCLUDE] QUEUE=[QUEUE] VERBOSE=1 PIDFILE=/var/run/resque/worker_[QUEUE].pid nohup php -f [PATH/TO/RESQUE]/resque.php > /var/log/resque/worker_[QUEUE].log &'" as uid [UID] and gid [GID] + start program = "/bin/sh -c 'APP_INCLUDE=[APP_INCLUDE] QUEUE=[QUEUE] VERBOSE=1 PIDFILE=/var/run/resque/worker_[QUEUE].pid nohup php -f [PATH/TO/RESQUE]/bin/resque > /var/log/resque/worker_[QUEUE].log &'" as uid [UID] and gid [GID] stop program = "/bin/sh -c 'kill -s QUIT `cat /var/run/resque/worker_[QUEUE].pid` && rm -f /var/run/resque/worker_[QUEUE].pid; exit 0;'" if totalmem is greater than 300 MB for 10 cycles then restart # eating up memory? group resque_workers \ No newline at end of file diff --git a/lib/Redisent/LICENSE b/lib/Redisent/LICENSE deleted file mode 100644 index 385910f..0000000 --- a/lib/Redisent/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2009 Justin Poliey - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/lib/Redisent/README.markdown b/lib/Redisent/README.markdown deleted file mode 100644 index 3edb843..0000000 --- a/lib/Redisent/README.markdown +++ /dev/null @@ -1,67 +0,0 @@ -# Redisent - -Redisent is a simple, no-nonsense interface to the [Redis](http://code.google.com/p/redis/) key-value store for modest developers. -Due to the way it is implemented, it is flexible and tolerant of changes to the Redis protocol. - -## Getting to work - -If you're at all familiar with the Redis protocol and PHP objects, you've already mastered Redisent. -All Redisent does is map the Redis protocol to a PHP object, abstract away the nitty-gritty, and make the return values PHP compatible. - - require 'redisent.php'; - $redis = new Redisent('localhost'); - $redis->set('awesome', 'absolutely'); - echo sprintf('Is Redisent awesome? %s.\n', $redis->get('awesome')); - -You use the exact same command names, and the exact same argument order. **How wonderful.** How about a more complex example? - - require 'redisent.php'; - $redis = new Redisent('localhost'); - $redis->rpush('particles', 'proton'); - $redis->rpush('particles', 'electron'); - $redis->rpush('particles', 'neutron'); - $particles = $redis->lrange('particles', 0, -1); - $particle_count = $redis->llen('particles'); - echo "

The {$particle_count} particles that make up atoms are:

"; - echo "
    "; - foreach ($particles as $particle) { - echo "
  • {$particle}
  • "; - } - echo "
"; - -Be aware that Redis error responses will be wrapped in a RedisException class and thrown, so do be sure to use proper coding techniques. - -## Clustering your servers - -Redisent also includes a way for developers to fully utilize the scalability of Redis with multiple servers and [consistent hashing](http://en.wikipedia.org/wiki/Consistent_hashing). -Using the RedisentCluster class, you can use Redisent the same way, except that keys will be hashed across multiple servers. -Here is how to set up a cluster: - - include 'redisent_cluster.php'; - - $cluster = new RedisentCluster(array( - array('host' => '127.0.0.1', 'port' => 6379), - array('host' => '127.0.0.1', 'port' => 6380) - )); - -You can then use Redisent the way you normally would, i.e., `$cluster->set('key', 'value')` or `$cluster->lrange('particles', 0, -1)`. -But what about when you need to use commands that are server specific and do not operate on keys? You can use routing, with the `RedisentCluster::to` method. -To use routing, you need to assign a server an alias in the constructor of the Redis cluster. Aliases are not required on all servers, just the ones you want to be able to access directly. - - include 'redisent_cluster.php'; - - $cluster = new RedisentCluster(array( - 'alpha' => array('host' => '127.0.0.1', 'port' => 6379), - array('host' => '127.0.0.1', 'port' => 6380) - )); - -Now there is an alias of the server running on 127.0.0.1:6379 called **alpha**, and can be interacted with like this: - - // get server info - $cluster->to('alpha')->info(); - -Now you have complete programatic control over your Redis servers. - -## About - -© 2009 [Justin Poliey](http://justinpoliey.com) \ No newline at end of file diff --git a/lib/Redisent/Redisent.php b/lib/Redisent/Redisent.php deleted file mode 100644 index 1580b89..0000000 --- a/lib/Redisent/Redisent.php +++ /dev/null @@ -1,150 +0,0 @@ - - * @copyright 2009 Justin Poliey - * @license http://www.opensource.org/licenses/mit-license.php The MIT License - * @package Redisent - */ - -define('CRLF', sprintf('%s%s', chr(13), chr(10))); - -/** - * Wraps native Redis errors in friendlier PHP exceptions - * Only declared if class doesn't already exist to ensure compatibility with php-redis - */ -if (! class_exists('RedisException')) { - class RedisException extends Exception { - } -} - -/** - * Redisent, a Redis interface for the modest among us - */ -class Redisent { - - /** - * Socket connection to the Redis server - * @var resource - * @access private - */ - private $__sock; - - /** - * Host of the Redis server - * @var string - * @access public - */ - public $host; - - /** - * Port on which the Redis server is running - * @var integer - * @access public - */ - public $port; - - /** - * Creates a Redisent connection to the Redis server on host {@link $host} and port {@link $port}. - * @param string $host The hostname of the Redis server - * @param integer $port The port number of the Redis server - */ - function __construct($host, $port = 6379) { - $this->host = $host; - $this->port = $port; - $this->establishConnection(); - } - - function establishConnection() { - $this->__sock = fsockopen($this->host, $this->port, $errno, $errstr); - if (!$this->__sock) { - throw new Exception("{$errno} - {$errstr}"); - } - } - - function __destruct() { - fclose($this->__sock); - } - - function __call($name, $args) { - - /* Build the Redis unified protocol command */ - array_unshift($args, strtoupper($name)); - $command = sprintf('*%d%s%s%s', count($args), CRLF, implode(array_map(array($this, 'formatArgument'), $args), CRLF), CRLF); - - /* Open a Redis connection and execute the command */ - for ($written = 0; $written < strlen($command); $written += $fwrite) { - $fwrite = fwrite($this->__sock, substr($command, $written)); - if ($fwrite === FALSE) { - throw new Exception('Failed to write entire command to stream'); - } - } - - /* Parse the response based on the reply identifier */ - $reply = trim(fgets($this->__sock, 512)); - switch (substr($reply, 0, 1)) { - /* Error reply */ - case '-': - throw new RedisException(substr(trim($reply), 4)); - break; - /* Inline reply */ - case '+': - $response = substr(trim($reply), 1); - break; - /* Bulk reply */ - case '$': - $response = null; - if ($reply == '$-1') { - break; - } - $read = 0; - $size = substr($reply, 1); - do { - $block_size = ($size - $read) > 1024 ? 1024 : ($size - $read); - $response .= fread($this->__sock, $block_size); - $read += $block_size; - } while ($read < $size); - fread($this->__sock, 2); /* discard crlf */ - break; - /* Multi-bulk reply */ - case '*': - $count = substr($reply, 1); - if ($count == '-1') { - return null; - } - $response = array(); - for ($i = 0; $i < $count; $i++) { - $bulk_head = trim(fgets($this->__sock, 512)); - $size = substr($bulk_head, 1); - if ($size == '-1') { - $response[] = null; - } - else { - $read = 0; - $block = ""; - do { - $block_size = ($size - $read) > 1024 ? 1024 : ($size - $read); - $block .= fread($this->__sock, $block_size); - $read += $block_size; - } while ($read < $size); - fread($this->__sock, 2); /* discard crlf */ - $response[] = $block; - } - } - break; - /* Integer reply */ - case ':': - $response = intval(substr(trim($reply), 1)); - break; - default: - throw new RedisException("invalid server response: {$reply}"); - break; - } - /* Party on */ - return $response; - } - - private function formatArgument($arg) { - return sprintf('$%d%s%s', strlen($arg), CRLF, $arg); - } -} \ No newline at end of file diff --git a/lib/Redisent/RedisentCluster.php b/lib/Redisent/RedisentCluster.php deleted file mode 100644 index ea93611..0000000 --- a/lib/Redisent/RedisentCluster.php +++ /dev/null @@ -1,138 +0,0 @@ - - * @copyright 2009 Justin Poliey - * @license http://www.opensource.org/licenses/mit-license.php The MIT License - * @package Redisent - */ - -require_once dirname(__FILE__) . '/Redisent.php'; - -/** - * A generalized Redisent interface for a cluster of Redis servers - */ -class RedisentCluster { - - /** - * Collection of Redisent objects attached to Redis servers - * @var array - * @access private - */ - private $redisents; - - /** - * Aliases of Redisent objects attached to Redis servers, used to route commands to specific servers - * @see RedisentCluster::to - * @var array - * @access private - */ - private $aliases; - - /** - * Hash ring of Redis server nodes - * @var array - * @access private - */ - private $ring; - - /** - * Individual nodes of pointers to Redis servers on the hash ring - * @var array - * @access private - */ - private $nodes; - - /** - * Number of replicas of each node to make around the hash ring - * @var integer - * @access private - */ - private $replicas = 128; - - /** - * The commands that are not subject to hashing - * @var array - * @access private - */ - private $dont_hash = array( - 'RANDOMKEY', 'DBSIZE', - 'SELECT', 'MOVE', 'FLUSHDB', 'FLUSHALL', - 'SAVE', 'BGSAVE', 'LASTSAVE', 'SHUTDOWN', - 'INFO', 'MONITOR', 'SLAVEOF' - ); - - /** - * Creates a Redisent interface to a cluster of Redis servers - * @param array $servers The Redis servers in the cluster. Each server should be in the format array('host' => hostname, 'port' => port) - */ - function __construct($servers) { - $this->ring = array(); - $this->aliases = array(); - foreach ($servers as $alias => $server) { - $this->redisents[] = new Redisent($server['host'], $server['port']); - if (is_string($alias)) { - $this->aliases[$alias] = $this->redisents[count($this->redisents)-1]; - } - for ($replica = 1; $replica <= $this->replicas; $replica++) { - $this->ring[crc32($server['host'].':'.$server['port'].'-'.$replica)] = $this->redisents[count($this->redisents)-1]; - } - } - ksort($this->ring, SORT_NUMERIC); - $this->nodes = array_keys($this->ring); - } - - /** - * Routes a command to a specific Redis server aliased by {$alias}. - * @param string $alias The alias of the Redis server - * @return Redisent The Redisent object attached to the Redis server - */ - function to($alias) { - if (isset($this->aliases[$alias])) { - return $this->aliases[$alias]; - } - else { - throw new Exception("That Redisent alias does not exist"); - } - } - - /* Execute a Redis command on the cluster */ - function __call($name, $args) { - - /* Pick a server node to send the command to */ - $name = strtoupper($name); - if (!in_array($name, $this->dont_hash)) { - $node = $this->nextNode(crc32($args[0])); - $redisent = $this->ring[$node]; - } - else { - $redisent = $this->redisents[0]; - } - - /* Execute the command on the server */ - return call_user_func_array(array($redisent, $name), $args); - } - - /** - * Routes to the proper server node - * @param integer $needle The hash value of the Redis command - * @return Redisent The Redisent object associated with the hash - */ - private function nextNode($needle) { - $haystack = $this->nodes; - while (count($haystack) > 2) { - $try = floor(count($haystack) / 2); - if ($haystack[$try] == $needle) { - return $needle; - } - if ($needle < $haystack[$try]) { - $haystack = array_slice($haystack, 0, $try + 1); - } - if ($needle > $haystack[$try]) { - $haystack = array_slice($haystack, $try + 1); - } - } - return $haystack[count($haystack)-1]; - } - -} \ No newline at end of file diff --git a/lib/Resque.php b/lib/Resque.php index 29249f0..bd29e84 100644 --- a/lib/Resque.php +++ b/lib/Resque.php @@ -1,18 +1,14 @@ - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque { - const VERSION = '1.0'; + const VERSION = '1.2'; const DEFAULT_INTERVAL = 5; @@ -32,12 +28,6 @@ class Resque */ protected static $redisDatabase = 0; - /** - * @var int PID of current process. Used to detect changes when forking - * and implement "thread" safety to avoid race conditions. - */ - protected static $pid = null; - /** * Given a host/port combination separated by a colon, set it as * the redis server that Resque will talk to. @@ -60,15 +50,7 @@ class Resque */ public static function redis() { - // Detect when the PID of the current process has changed (from a fork, etc) - // and force a reconnect to redis. - $pid = getmypid(); - if (self::$pid !== $pid) { - self::$redis = null; - self::$pid = $pid; - } - - if(!is_null(self::$redis)) { + if (self::$redis !== null) { return self::$redis; } @@ -77,24 +59,35 @@ class Resque $server = 'localhost:6379'; } - if(is_array($server)) { - require_once dirname(__FILE__) . '/Resque/RedisCluster.php'; - self::$redis = new Resque_RedisCluster($server); - } - else { - if (strpos($server, 'unix:') === false) { - list($host, $port) = explode(':', $server); - } - else { - $host = $server; - $port = null; - } - require_once dirname(__FILE__) . '/Resque/Redis.php'; - self::$redis = new Resque_Redis($host, $port); + self::$redis = new Resque_Redis($server, self::$redisDatabase); + return self::$redis; + } + + /** + * fork() helper method for php-resque that handles issues PHP socket + * and phpredis have with passing around sockets between child/parent + * processes. + * + * Will close connection to Redis before forking. + * + * @return int Return vars as per pcntl_fork() + */ + public static function fork() + { + if(!function_exists('pcntl_fork')) { + return -1; } - self::$redis->select(self::$redisDatabase); - return self::$redis; + // Close the connection to Redis before forking. + // This is a workaround for issues phpredis has. + self::$redis = null; + + $pid = pcntl_fork(); + if($pid === -1) { + throw new RuntimeException('Unable to fork child worker.'); + } + + return $pid; } /** @@ -156,7 +149,6 @@ class Resque */ public static function enqueue($queue, $class, $args = null, $trackStatus = false) { - require_once dirname(__FILE__) . '/Resque/Job.php'; $result = Resque_Job::create($queue, $class, $args, $trackStatus); if ($result) { Resque_Event::trigger('afterEnqueue', array( @@ -177,7 +169,6 @@ class Resque */ public static function reserve($queue, $interval = null) { - require_once dirname(__FILE__) . '/Resque/Job.php'; return Resque_Job::reserve($queue, $interval); } diff --git a/lib/Resque/Event.php b/lib/Resque/Event.php index 930c067..20072ff 100644 --- a/lib/Resque/Event.php +++ b/lib/Resque/Event.php @@ -3,8 +3,7 @@ * Resque event/plugin system class * * @package Resque/Event - * @author Chris Boulton - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Event diff --git a/lib/Resque/Exception.php b/lib/Resque/Exception.php index b288bf4..60cca86 100644 --- a/lib/Resque/Exception.php +++ b/lib/Resque/Exception.php @@ -3,8 +3,7 @@ * Resque exception. * * @package Resque - * @author Chris Boulton - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Exception extends Exception diff --git a/lib/Resque/Failure.php b/lib/Resque/Failure.php index 9f6d89a..deb678f 100644 --- a/lib/Resque/Failure.php +++ b/lib/Resque/Failure.php @@ -1,12 +1,10 @@ - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Failure @@ -38,7 +36,6 @@ class Resque_Failure public static function getBackend() { if(self::$backend === null) { - require dirname(__FILE__) . '/Failure/Redis.php'; self::$backend = 'Resque_Failure_Redis'; } diff --git a/lib/Resque/Failure/Interface.php b/lib/Resque/Failure/Interface.php index 863cd0b..b7e5bc8 100644 --- a/lib/Resque/Failure/Interface.php +++ b/lib/Resque/Failure/Interface.php @@ -3,8 +3,7 @@ * Interface that all failure backends should implement. * * @package Resque/Failure - * @author Chris Boulton - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ interface Resque_Failure_Interface diff --git a/lib/Resque/Failure/Redis.php b/lib/Resque/Failure/Redis.php index c81bfc2..cfac5b6 100644 --- a/lib/Resque/Failure/Redis.php +++ b/lib/Resque/Failure/Redis.php @@ -3,8 +3,7 @@ * Redis backend for storing failed Resque jobs. * * @package Resque/Failure - * @author Chris Boulton - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ diff --git a/lib/Resque/Job.php b/lib/Resque/Job.php index 6af0219..5066c27 100755 --- a/lib/Resque/Job.php +++ b/lib/Resque/Job.php @@ -1,14 +1,9 @@ - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Job @@ -209,7 +204,6 @@ class Resque_Job )); $this->updateStatus(Resque_Job_Status::STATUS_FAILED); - require_once dirname(__FILE__) . '/Failure.php'; Resque_Failure::create( $this->payload, $exception, diff --git a/lib/Resque/Job/DirtyExitException.php b/lib/Resque/Job/DirtyExitException.php index b69413a..108e061 100644 --- a/lib/Resque/Job/DirtyExitException.php +++ b/lib/Resque/Job/DirtyExitException.php @@ -3,8 +3,7 @@ * Runtime exception class for a job that does not exit cleanly. * * @package Resque/Job - * @author Chris Boulton - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Job_DirtyExitException extends RuntimeException diff --git a/lib/Resque/Job/DontPerform.php b/lib/Resque/Job/DontPerform.php index 91d5c70..553327f 100644 --- a/lib/Resque/Job/DontPerform.php +++ b/lib/Resque/Job/DontPerform.php @@ -3,8 +3,7 @@ * Exception to be thrown if a job should not be performed/run. * * @package Resque/Job - * @author Chris Boulton - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Job_DontPerform extends Exception diff --git a/lib/Resque/Job/Status.php b/lib/Resque/Job/Status.php index e1554b0..ffa351b 100644 --- a/lib/Resque/Job/Status.php +++ b/lib/Resque/Job/Status.php @@ -3,8 +3,7 @@ * Status tracker/information for a job. * * @package Resque/Job - * @author Chris Boulton - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Job_Status diff --git a/lib/Resque/Redis.php b/lib/Resque/Redis.php index e1f077f..4447146 100644 --- a/lib/Resque/Redis.php +++ b/lib/Resque/Redis.php @@ -1,26 +1,22 @@ - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ -class Resque_Redis extends Redisent +class Resque_Redis { /** * Redis namespace * @var string */ private static $defaultNamespace = 'resque:'; + + private $server; + private $database; + /** * @var array List of all commands in Redis that supply a key as their * first argument. Used to prefix keys with the Resque namespace. @@ -34,6 +30,7 @@ class Resque_Redis extends Redisent 'ttl', 'move', 'set', + 'setex', 'get', 'getset', 'setnx', @@ -82,7 +79,7 @@ class Resque_Redis extends Redisent // msetnx // mset // renamenx - + /** * Set Redis namespace (prefix) default: resque * @param string $namespace @@ -94,7 +91,48 @@ class Resque_Redis extends Redisent } self::$defaultNamespace = $namespace; } - + + public function __construct($server, $database = null) + { + $this->server = $server; + $this->database = $database; + + if (is_array($this->server)) { + $this->driver = new Credis_Cluster($server); + } + else { + $port = null; + $password = null; + $host = $server; + + // If not a UNIX socket path or tcp:// formatted connections string + // assume host:port combination. + if (strpos($server, '/') === false) { + $parts = explode(':', $server); + if (isset($parts[1])) { + $port = $parts[1]; + } + $host = $parts[0]; + }else if (strpos($server, 'redis://') !== false){ + // Redis format is: + // redis://[user]:[password]@[host]:[port] + list($userpwd,$hostport) = explode('@', $server); + $userpwd = substr($userpwd, strpos($userpwd, 'redis://')+8); + list($host, $port) = explode(':', $hostport); + list($user, $password) = explode(':', $userpwd); + } + + $this->driver = new Credis_Client($host, $port); + if (isset($password)){ + $this->driver->auth($password); + } + } + + if ($this->database !== null) { + $this->driver->select($database); + } + } + /** * Magic method to handle all function requests and prefix key based * operations with the {self::$defaultNamespace} key prefix. @@ -104,16 +142,30 @@ class Resque_Redis extends Redisent * @return mixed Return value from Resident::call() based on the command. */ public function __call($name, $args) { - $args = func_get_args(); if(in_array($name, $this->keyCommands)) { - $args[1][0] = self::$defaultNamespace . $args[1][0]; + $args[0] = self::$defaultNamespace . $args[0]; } try { - return parent::__call($name, $args[1]); + return $this->driver->__call($name, $args); } - catch(RedisException $e) { + catch(CredisException $e) { return false; } } + + public static function getPrefix() + { + return self::$defaultNamespace; + } + + public static function removePrefix($string) + { + $prefix=self::getPrefix(); + + if (substr($string, 0, strlen($prefix)) == $prefix) { + $string = substr($string, strlen($prefix), strlen($string) ); + } + return $string; + } } ?> \ No newline at end of file diff --git a/lib/Resque/RedisCluster.php b/lib/Resque/RedisCluster.php deleted file mode 100644 index e884653..0000000 --- a/lib/Resque/RedisCluster.php +++ /dev/null @@ -1,119 +0,0 @@ - - * @copyright (c) 2010 Chris Boulton - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_RedisCluster extends RedisentCluster -{ - /** - * Redis namespace - * @var string - */ - private static $defaultNamespace = 'resque:'; - /** - * @var array List of all commands in Redis that supply a key as their - * first argument. Used to prefix keys with the Resque namespace. - */ - private $keyCommands = array( - 'exists', - 'del', - 'type', - 'keys', - 'expire', - 'ttl', - 'move', - 'set', - 'get', - 'getset', - 'setnx', - 'incr', - 'incrby', - 'decrby', - 'decrby', - 'rpush', - 'lpush', - 'llen', - 'lrange', - 'ltrim', - 'lindex', - 'lset', - 'lrem', - 'lpop', - 'blpop', - 'rpop', - 'sadd', - 'srem', - 'spop', - 'scard', - 'sismember', - 'smembers', - 'srandmember', - 'zadd', - 'zrem', - 'zrange', - 'zrevrange', - 'zrangebyscore', - 'zcard', - 'zscore', - 'zremrangebyscore', - 'sort' - ); - // sinterstore - // sunion - // sunionstore - // sdiff - // sdiffstore - // sinter - // smove - // rename - // rpoplpush - // mget - // msetnx - // mset - // renamenx - - /** - * Set Redis namespace (prefix) default: resque - * @param string $namespace - */ - public static function prefix($namespace) - { - if (strpos($namespace, ':') === false) { - $namespace .= ':'; - } - self::$defaultNamespace = $namespace; - } - - /** - * Magic method to handle all function requests and prefix key based - * operations with the '{self::$defaultNamespace}' key prefix. - * - * @param string $name The name of the method called. - * @param array $args Array of supplied arguments to the method. - * @return mixed Return value from Resident::call() based on the command. - */ - public function __call($name, $args) { - $args = func_get_args(); - if(in_array($name, $this->keyCommands)) { - $args[1][0] = self::$defaultNamespace . $args[1][0]; - } - try { - return parent::__call($name, $args[1]); - } - catch(RedisException $e) { - return false; - } - } -} -?> diff --git a/lib/Resque/Stat.php b/lib/Resque/Stat.php index 2805376..bc00c63 100644 --- a/lib/Resque/Stat.php +++ b/lib/Resque/Stat.php @@ -3,8 +3,7 @@ * Resque statistic management (jobs processed, failed, etc) * * @package Resque/Stat - * @author Chris Boulton - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Stat diff --git a/lib/Resque/Worker.php b/lib/Resque/Worker.php index 58571be..204aae6 100644 --- a/lib/Resque/Worker.php +++ b/lib/Resque/Worker.php @@ -1,16 +1,10 @@ - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Worker @@ -190,7 +184,7 @@ class Resque_Worker Resque_Event::trigger('beforeFork', $job); $this->workingOn($job); - $this->child = $this->fork(); + $this->child = Resque::fork(); // Forked and we're the child. Run the job. if ($this->child === 0 || $this->child === false) { @@ -292,27 +286,6 @@ class Resque_Worker return $queues; } - /** - * Attempt to fork a child process from the parent to run a job in. - * - * Return values are those of pcntl_fork(). - * - * @return int -1 if the fork failed, 0 for the forked child, the PID of the child for the parent. - */ - private function fork() - { - if(!function_exists('pcntl_fork')) { - return false; - } - - $pid = pcntl_fork(); - if($pid === -1) { - throw new RuntimeException('Unable to fork child worker.'); - } - - return $pid; - } - /** * Perform necessary actions to start a worker. */ @@ -480,7 +453,7 @@ class Resque_Worker */ public function registerWorker() { - Resque::redis()->sadd('workers', $this); + Resque::redis()->sadd('workers', (string)$this); Resque::redis()->set('worker:' . (string)$this . ':started', strftime('%a %b %d %H:%M:%S %Z %Y')); } @@ -544,16 +517,21 @@ class Resque_Worker /** * Output a given log message to STDOUT. * - * @param string $message Message to output. + * @param string $message Message to output. + * @param int $logLevel The logging level to capture */ - public function log($message) + public function log($message, $logLevel = self::LOG_NORMAL) { - if($this->logLevel == self::LOG_NORMAL) { + if ($logLevel > $this->logLevel) { + return; + } + + if ($this->logLevel == self::LOG_NORMAL) { fwrite(STDOUT, "*** " . $message . "\n"); + return; } - else if($this->logLevel == self::LOG_VERBOSE) { - fwrite(STDOUT, "** [" . strftime('%T %Y-%m-%d') . "] " . $message . "\n"); - } + + fwrite(STDOUT, "** [" . strftime('%T %Y-%m-%d') . "] " . $message . "\n"); } /** diff --git a/phpunit.xml b/phpunit.xml index efbc7f2..61d2d7b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,8 @@ 1) { - $count = $COUNT; -} - -if($count > 1) { - for($i = 0; $i < $count; ++$i) { - $pid = pcntl_fork(); - if($pid == -1) { - die("Could not fork worker ".$i."\n"); - } - // Child, start the worker - else if(!$pid) { - $queues = explode(',', $QUEUE); - $worker = new Resque_Worker($queues); - $worker->logLevel = $logLevel; - fwrite(STDOUT, '*** Starting worker '.$worker."\n"); - $worker->work($interval); - break; - } - } -} -// Start a single worker -else { - $queues = explode(',', $QUEUE); - $worker = new Resque_Worker($queues); - $worker->logLevel = $logLevel; - - $PIDFILE = getenv('PIDFILE'); - if ($PIDFILE) { - file_put_contents($PIDFILE, getmypid()) or - die('Could not write PID information to ' . $PIDFILE); - } - - fwrite(STDOUT, '*** Starting worker '.$worker."\n"); - $worker->work($interval); -} -?> diff --git a/test/Resque/Tests/EventTest.php b/test/Resque/Tests/EventTest.php index 0bf5ee9..880a323 100644 --- a/test/Resque/Tests/EventTest.php +++ b/test/Resque/Tests/EventTest.php @@ -1,12 +1,9 @@ - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Tests_EventTest extends Resque_Tests_TestCase diff --git a/test/Resque/Tests/JobStatusTest.php b/test/Resque/Tests/JobStatusTest.php index 5f0fd0f..3a12b4b 100644 --- a/test/Resque/Tests/JobStatusTest.php +++ b/test/Resque/Tests/JobStatusTest.php @@ -1,12 +1,9 @@ - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Tests_JobStatusTest extends Resque_Tests_TestCase diff --git a/test/Resque/Tests/JobTest.php b/test/Resque/Tests/JobTest.php index 3102291..5d93d6f 100644 --- a/test/Resque/Tests/JobTest.php +++ b/test/Resque/Tests/JobTest.php @@ -1,12 +1,10 @@ - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Tests_JobTest extends Resque_Tests_TestCase diff --git a/test/Resque/Tests/StatTest.php b/test/Resque/Tests/StatTest.php index 6404794..aa41888 100644 --- a/test/Resque/Tests/StatTest.php +++ b/test/Resque/Tests/StatTest.php @@ -1,12 +1,9 @@ - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Tests_StatTest extends Resque_Tests_TestCase diff --git a/test/Resque/Tests/TestCase.php b/test/Resque/Tests/TestCase.php index f4c00df..4ed65de 100644 --- a/test/Resque/Tests/TestCase.php +++ b/test/Resque/Tests/TestCase.php @@ -3,8 +3,7 @@ * Resque test case class. Contains setup and teardown methods. * * @package Resque/Tests - * @author Chris Boulton - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Tests_TestCase extends PHPUnit_Framework_TestCase @@ -16,7 +15,7 @@ class Resque_Tests_TestCase extends PHPUnit_Framework_TestCase { $config = file_get_contents(REDIS_CONF); preg_match('#^\s*port\s+([0-9]+)#m', $config, $matches); - $this->redis = new Redisent('localhost', $matches[1]); + $this->redis = new Credis_Client('localhost', $matches[1]); // Flush redis $this->redis->flushAll(); diff --git a/test/Resque/Tests/WorkerTest.php b/test/Resque/Tests/WorkerTest.php index 47b0208..b2f0e00 100644 --- a/test/Resque/Tests/WorkerTest.php +++ b/test/Resque/Tests/WorkerTest.php @@ -1,12 +1,9 @@ - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ class Resque_Tests_WorkerTest extends Resque_Tests_TestCase diff --git a/test/Resque/Tests/bootstrap.php b/test/bootstrap.php similarity index 83% rename from test/Resque/Tests/bootstrap.php rename to test/bootstrap.php index 0efa2a8..a4b6837 100644 --- a/test/Resque/Tests/bootstrap.php +++ b/test/bootstrap.php @@ -3,26 +3,16 @@ * Resque test bootstrap file - sets up a test environment. * * @package Resque/Tests - * @author Chris Boulton - * @copyright (c) 2010 Chris Boulton + * @author Chris Boulton * @license http://www.opensource.org/licenses/mit-license.php */ -define('CWD', dirname(__FILE__)); -define('RESQUE_LIB', CWD . '/../../../lib/'); -define('TEST_MISC', realpath(CWD . '/../../misc/')); +$loader = require __DIR__ . '/../vendor/autoload.php'; +$loader->add('Resque_Tests', __DIR__); + +define('TEST_MISC', realpath(__DIR__ . '/misc/')); define('REDIS_CONF', TEST_MISC . '/redis.conf'); -// Change to the directory this file lives in. This is important, due to -// how we'll be running redis. - -require_once CWD . '/TestCase.php'; - -// Include Resque -require_once RESQUE_LIB . 'Resque.php'; -require_once RESQUE_LIB . 'Resque/Worker.php'; -require_once RESQUE_LIB . 'Resque/Redis.php'; - // Attempt to start our own redis instance for tesitng. exec('which redis-server', $output, $returnVar); if($returnVar != 0) { @@ -62,7 +52,7 @@ function killRedis($pid) if (file_exists($pidFile)) { $pid = trim(file_get_contents($pidFile)); posix_kill((int) $pid, 9); - + if(is_file($pidFile)) { unlink($pidFile); }