diff --git a/.gitignore b/.gitignore index a47ff2d2..d1502b08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ vendor/ -*.swp +composer.lock diff --git a/HOWITWORKS.md b/HOWITWORKS.md index ec85fa37..ad4f5a21 100644 --- a/HOWITWORKS.md +++ b/HOWITWORKS.md @@ -4,154 +4,154 @@ The following is a step-by-step breakdown of how php-resque operates. ## Enqueue Job ## -What happens when you call `Resque::enqueue()`? +What happens when you call `Resque\Resque::enqueue()`? -1. `Resque::enqueue()` calls `Resque_Job::create()` with the same arguments it +1. `Resque\Resque::enqueue()` calls `Resque\Job::create()` with the same arguments it received. -2. `Resque_Job::create()` checks that your `$args` (the third argument) are +2. `Resque\Job::create()` checks that your `$args` (the third argument) are either `null` or in an array -3. `Resque_Job::create()` generates a job ID (a "token" in most of the docs) -4. `Resque_Job::create()` pushes the job to the requested queue (first +3. `Resque\Job::create()` generates a job ID (a "token" in most of the docs) +4. `Resque\Job::create()` pushes the job to the requested queue (first argument) -5. `Resque_Job::create()`, if status monitoring is enabled for the job (fourth - argument), calls `Resque_Job_Status::create()` with the job ID as its only +5. `Resque\Job::create()`, if status monitoring is enabled for the job (fourth + argument), calls `Resque\Job\Status::create()` with the job ID as its only argument -6. `Resque_Job_Status::create()` creates a key in Redis with the job ID in its +6. `Resque\Job\Status::create()` creates a key in Redis with the job ID in its name, and the current status (as well as a couple of timestamps) as its - value, then returns control to `Resque_Job::create()` -7. `Resque_Job::create()` returns control to `Resque::enqueue()`, with the job + value, then returns control to `Resque\Job::create()` +7. `Resque\Job::create()` returns control to `Resque\Resque::enqueue()`, with the job ID as a return value -8. `Resque::enqueue()` triggers the `afterEnqueue` event, then returns control +8. `Resque\Resque::enqueue()` triggers the `afterEnqueue` event, then returns control to your application, again with the job ID as its return value ## Workers At Work ## How do the workers process the queues? -1. `Resque_Worker::work()`, the main loop of the worker process, calls - `Resque_Worker->reserve()` to check for a job -2. `Resque_Worker->reserve()` checks whether to use blocking pops or not (from +1. `Resque\Worker::work()`, the main loop of the worker process, calls + `Resque\Worker->reserve()` to check for a job +2. `Resque\Worker->reserve()` checks whether to use blocking pops or not (from `BLOCKING`), then acts accordingly: * Blocking Pop - 1. `Resque_Worker->reserve()` calls `Resque_Job::reserveBlocking()` with + 1. `Resque\Worker->reserve()` calls `Resque\Job::reserveBlocking()` with the entire queue list and the timeout (from `INTERVAL`) as arguments - 2. `Resque_Job::reserveBlocking()` calls `Resque::blpop()` (which in turn + 2. `Resque\Job::reserveBlocking()` calls `Resque\Resque::blpop()` (which in turn calls Redis' `blpop`, after prepping the queue list for the call, then processes the response for consistency with other aspects of the library, before finally returning control [and the queue/content of the - retrieved job, if any] to `Resque_Job::reserveBlocking()`) - 3. `Resque_Job::reserveBlocking()` checks whether the job content is an + retrieved job, if any] to `Resque\Job::reserveBlocking()`) + 3. `Resque\Job::reserveBlocking()` checks whether the job content is an array (it should contain the job's type [class], payload [args], and ID), and aborts processing if not - 4. `Resque_Job::reserveBlocking()` creates a new `Resque_Job` object with + 4. `Resque\Job::reserveBlocking()` creates a new `Resque\Job` object with the queue and content as constructor arguments to initialize the job itself, and returns it, along with control of the process, to - `Resque_Worker->reserve()` + `Resque\Worker->reserve()` * Queue Polling - 1. `Resque_Worker->reserve()` iterates through the queue list, calling - `Resque_Job::reserve()` with the current queue's name as the sole + 1. `Resque\Worker->reserve()` iterates through the queue list, calling + `Resque\Job::reserve()` with the current queue's name as the sole argument on each pass - 2. `Resque_Job::reserve()` passes the queue name on to `Resque::pop()`, + 2. `Resque\Job::reserve()` passes the queue name on to `Resque\Resque::pop()`, which in turn calls Redis' `lpop` with the same argument, then returns - control (and the job content, if any) to `Resque_Job::reserve()` - 3. `Resque_Job::reserve()` checks whether the job content is an array (as + control (and the job content, if any) to `Resque\Job::reserve()` + 3. `Resque\Job::reserve()` checks whether the job content is an array (as before, it should contain the job's type [class], payload [args], and ID), and aborts processing if not - 4. `Resque_Job::reserve()` creates a new `Resque_Job` object in the same + 4. `Resque\Job::reserve()` creates a new `Resque\Job` object in the same manner as above, and also returns this object (along with control of - the process) to `Resque_Worker->reserve()` -3. In either case, `Resque_Worker->reserve()` returns the new `Resque_Job` - object, along with control, up to `Resque_Worker::work()`; if no job is + the process) to `Resque\Worker->reserve()` +3. In either case, `Resque\Worker->reserve()` returns the new `Resque\Job` + object, along with control, up to `Resque\Worker::work()`; if no job is found, it simply returns `FALSE` * No Jobs - 1. If blocking mode is not enabled, `Resque_Worker::work()` sleeps for + 1. If blocking mode is not enabled, `Resque\Worker::work()` sleeps for `INTERVAL` seconds; it calls `usleep()` for this, so fractional seconds *are* supported * Job Reserved - 1. `Resque_Worker::work()` triggers a `beforeFork` event - 2. `Resque_Worker::work()` calls `Resque_Worker->workingOn()` with the new - `Resque_Job` object as its argument - 3. `Resque_Worker->workingOn()` does some reference assignments to help keep + 1. `Resque\Worker::work()` triggers a `beforeFork` event + 2. `Resque\Worker::work()` calls `Resque\Worker->workingOn()` with the new + `Resque\Job` object as its argument + 3. `Resque\Worker->workingOn()` does some reference assignments to help keep track of the worker/job relationship, then updates the job status from `WAITING` to `RUNNING` - 4. `Resque_Worker->workingOn()` stores the new `Resque_Job` object's payload + 4. `Resque\Worker->workingOn()` stores the new `Resque\Job` object's payload in a Redis key associated to the worker itself (this is to prevent the job from being lost indefinitely, but does rely on that PID never being allocated on that host to a different worker process), then returns control - to `Resque_Worker::work()` - 5. `Resque_Worker::work()` forks a child process to run the actual `perform()` + to `Resque\Worker::work()` + 5. `Resque\Worker::work()` forks a child process to run the actual `perform()` 6. The next steps differ between the worker and the child, now running in separate processes: * Worker 1. The worker waits for the job process to complete - 2. If the exit status is not 0, the worker calls `Resque_Job->fail()` with - a `Resque_Job_DirtyExitException` as its only argument. - 3. `Resque_Job->fail()` triggers an `onFailure` event - 4. `Resque_Job->fail()` updates the job status from `RUNNING` to `FAILED` - 5. `Resque_Job->fail()` calls `Resque_Failure::create()` with the job - payload, the `Resque_Job_DirtyExitException`, the internal ID of the + 2. If the exit status is not 0, the worker calls `Resque\Job->fail()` with + a `Resque\Job\DirtyExitException` as its only argument. + 3. `Resque\Job->fail()` triggers an `onFailure` event + 4. `Resque\Job->fail()` updates the job status from `RUNNING` to `FAILED` + 5. `Resque\Job->fail()` calls `Resque\Failure::create()` with the job + payload, the `Resque\Job\DirtyExitException`, the internal ID of the worker, and the queue name as arguments - 6. `Resque_Failure::create()` creates a new object of whatever type has - been set as the `Resque_Failure` "backend" handler; by default, this is - a `Resque_Failure_Redis` object, whose constructor simply collects the - data passed into `Resque_Failure::create()` and pushes it into Redis + 6. `Resque\Failure::create()` creates a new object of whatever type has + been set as the `Resque\Failure` "backend" handler; by default, this is + a `Resque\Resque\Failure\Redis` object, whose constructor simply collects the + data passed into `Resque\Failure::create()` and pushes it into Redis in the `failed` queue - 7. `Resque_Job->fail()` increments two failure counters in Redis: one for + 7. `Resque\Job->fail()` increments two failure counters in Redis: one for a total count, and one for the worker - 8. `Resque_Job->fail()` returns control to the worker (still in - `Resque_Worker::work()`) without a value + 8. `Resque\Job->fail()` returns control to the worker (still in + `Resque\Worker::work()`) without a value * Job - 1. The job calls `Resque_Worker->perform()` with the `Resque_Job` as its + 1. The job calls `Resque\Worker->perform()` with the `Resque\Job` as its only argument. - 2. `Resque_Worker->perform()` sets up a `try...catch` block so it can + 2. `Resque\Worker->perform()` sets up a `try...catch` block so it can properly handle exceptions by marking jobs as failed (by calling - `Resque_Job->fail()`, as above) - 3. Inside the `try...catch`, `Resque_Worker->perform()` triggers an + `Resque\Job->fail()`, as above) + 3. Inside the `try...catch`, `Resque\Worker->perform()` triggers an `afterFork` event - 4. Still inside the `try...catch`, `Resque_Worker->perform()` calls - `Resque_Job->perform()` with no arguments - 5. `Resque_Job->perform()` calls `Resque_Job->getInstance()` with no + 4. Still inside the `try...catch`, `Resque\Worker->perform()` calls + `Resque\Job->perform()` with no arguments + 5. `Resque\Job->perform()` calls `Resque\Job->getInstance()` with no arguments - 6. If `Resque_Job->getInstance()` has already been called, it returns the + 6. If `Resque\Job->getInstance()` has already been called, it returns the existing instance; otherwise: - 7. `Resque_Job->getInstance()` checks that the job's class (type) exists + 7. `Resque\Job->getInstance()` checks that the job's class (type) exists and has a `perform()` method; if not, in either case, it throws an - exception which will be caught by `Resque_Worker->perform()` - 8. `Resque_Job->getInstance()` creates an instance of the job's class, and - initializes it with a reference to the `Resque_Job` itself, the job's - arguments (which it gets by calling `Resque_Job->getArguments()`, which + exception which will be caught by `Resque\Worker->perform()` + 8. `Resque\Job->getInstance()` creates an instance of the job's class, and + initializes it with a reference to the `Resque\Job` itself, the job's + arguments (which it gets by calling `Resque\Job->getArguments()`, which in turn simply returns the value of `args[0]`, or an empty array if no arguments were passed), and the queue name - 9. `Resque_Job->getInstance()` returns control, along with the job class - instance, to `Resque_Job->perform()` - 10. `Resque_Job->perform()` sets up its own `try...catch` block to handle - `Resque_Job_DontPerform` exceptions; any other exceptions are passed - up to `Resque_Worker->perform()` - 11. `Resque_Job->perform()` triggers a `beforePerform` event - 12. `Resque_Job->perform()` calls `setUp()` on the instance, if it exists - 13. `Resque_Job->perform()` calls `perform()` on the instance - 14. `Resque_Job->perform()` calls `tearDown()` on the instance, if it + 9. `Resque\Job->getInstance()` returns control, along with the job class + instance, to `Resque\Job->perform()` + 10. `Resque\Job->perform()` sets up its own `try...catch` block to handle + `Resque\Job\DontPerform` exceptions; any other exceptions are passed + up to `Resque\Worker->perform()` + 11. `Resque\Job->perform()` triggers a `beforePerform` event + 12. `Resque\Job->perform()` calls `setUp()` on the instance, if it exists + 13. `Resque\Job->perform()` calls `perform()` on the instance + 14. `Resque\Job->perform()` calls `tearDown()` on the instance, if it exists - 15. `Resque_Job->perform()` triggers an `afterPerform` event - 16. The `try...catch` block ends, suppressing `Resque_Job_DontPerform` + 15. `Resque\Job->perform()` triggers an `afterPerform` event + 16. The `try...catch` block ends, suppressing `Resque\Job\DontPerform` exceptions by returning control, and the value `FALSE`, to - `Resque_Worker->perform()`; any other situation returns the value + `Resque\Worker->perform()`; any other situation returns the value `TRUE` along with control, instead - 17. The `try...catch` block in `Resque_Worker->perform()` ends - 18. `Resque_Worker->perform()` updates the job status from `RUNNING` to + 17. The `try...catch` block in `Resque\Worker->perform()` ends + 18. `Resque\Worker->perform()` updates the job status from `RUNNING` to `COMPLETE`, then returns control, with no value, to the worker (again - still in `Resque_Worker::work()`) - 19. `Resque_Worker::work()` calls `exit(0)` to terminate the job process + still in `Resque\Worker::work()`) + 19. `Resque\Worker::work()` calls `exit(0)` to terminate the job process cleanly * SPECIAL CASE: Non-forking OS (Windows) 1. Same as the job above, except it doesn't call `exit(0)` when done - 7. `Resque_Worker::work()` calls `Resque_Worker->doneWorking()` with no + 7. `Resque\Worker::work()` calls `Resque\Worker->doneWorking()` with no arguments - 8. `Resque_Worker->doneWorking()` increments two processed counters in Redis: + 8. `Resque\Worker->doneWorking()` increments two processed counters in Redis: one for a total count, and one for the worker - 9. `Resque_Worker->doneWorking()` deletes the Redis key set in - `Resque_Worker->workingOn()`, then returns control, with no value, to - `Resque_Worker::work()` -4. `Resque_Worker::work()` returns control to the beginning of the main loop, + 9. `Resque\Worker->doneWorking()` deletes the Redis key set in + `Resque\Worker->workingOn()`, then returns control, with no value, to + `Resque\Worker::work()` +4. `Resque\Worker::work()` returns control to the beginning of the main loop, where it will wait for the next job to become available, and start this process all over again \ No newline at end of file diff --git a/README.md b/README.md index a254bc01..91b4dd02 100644 --- a/README.md +++ b/README.md @@ -78,12 +78,12 @@ Jobs are queued as follows: ```php // Required if redis is located elsewhere -Resque::setBackend('localhost:6379'); +Resque\Resque::setBackend('localhost:6379'); $args = array( 'name' => 'Chris' ); -Resque::enqueue('default', 'My_Job', $args); +Resque\Resque::enqueue('default', 'My_Job', $args); ``` ### Defining Jobs ### @@ -140,23 +140,23 @@ This method can be used to conveniently remove a job from a queue. ```php // Removes job class 'My_Job' of queue 'default' -Resque::dequeue('default', ['My_Job']); +Resque\Resque::dequeue('default', ['My_Job']); // Removes job class 'My_Job' with Job ID '087df5819a790ac666c9608e2234b21e' of queue 'default' Resuque::dequeue('default', ['My_Job' => '087df5819a790ac666c9608e2234b21e']); // Removes job class 'My_Job' with arguments of queue 'default' -Resque::dequeue('default', ['My_Job' => array('foo' => 1, 'bar' => 2)]); +Resque\Resque::dequeue('default', ['My_Job' => array('foo' => 1, 'bar' => 2)]); // Removes multiple jobs -Resque::dequeue('default', ['My_Job', 'My_Job2']); +Resque\Resque::dequeue('default', ['My_Job', 'My_Job2']); ``` If no jobs are given, this method will dequeue all jobs matching the provided queue. ```php // Removes all jobs of queue 'default' -Resque::dequeue('default'); +Resque\Resque::dequeue('default'); ``` ### Tracking Job Statuses ### @@ -166,28 +166,28 @@ job. The status information will allow you to check if a job is in the queue, is currently being run, has finished, or has failed. To track the status of a job, pass `true` as the fourth argument to -`Resque::enqueue`. A token used for tracking the job status will be +`Resque\Resque::enqueue`. A token used for tracking the job status will be returned: ```php -$token = Resque::enqueue('default', 'My_Job', $args, true); +$token = Resque\Resque::enqueue('default', 'My_Job', $args, true); echo $token; ``` To fetch the status of a job: ```php -$status = new Resque_Job_Status($token); +$status = new Resque\Job\Status($token); echo $status->get(); // Outputs the status ``` -Job statuses are defined as constants in the `Resque_Job_Status` class. +Job statuses are defined as constants in the `Resque\Job\Status` class. Valid statuses include: -* `Resque_Job_Status::STATUS_WAITING` - Job is still queued -* `Resque_Job_Status::STATUS_RUNNING` - Job is currently running -* `Resque_Job_Status::STATUS_FAILED` - Job has failed -* `Resque_Job_Status::STATUS_COMPLETE` - Job is complete +* `Resque\Job\Status::STATUS_WAITING` - Job is still queued +* `Resque\Job\Status::STATUS_RUNNING` - Job is currently running +* `Resque\Job\Status::STATUS_FAILED` - Job has failed +* `Resque\Job\Status::STATUS_COMPLETE` - Job is complete * `false` - Failed to fetch the status - is the token valid? Statuses are available for up to 24 hours after a job has completed @@ -334,12 +334,12 @@ automatically detect and use it. php-resque has a basic event system that can be used by your application to customize how some of the php-resque internals behave. -You listen in on events (as listed below) by registering with `Resque_Event` +You listen in on events (as listed below) by registering with `Resque\Event` and supplying a callback that you would like triggered when the event is raised: ```sh -Resque_Event::listen('eventName', [callback]); +Resque\Event::listen('eventName', [callback]); ``` `[callback]` may be anything in PHP that is callable by `call_user_func_array`: @@ -352,12 +352,12 @@ Resque_Event::listen('eventName', [callback]); Events may pass arguments (documented below), so your callback should accept these arguments. -You can stop listening to an event by calling `Resque_Event::stopListening` -with the same arguments supplied to `Resque_Event::listen`. +You can stop listening to an event by calling `Resque\Event::stopListening` +with the same arguments supplied to `Resque\Event::listen`. 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`. +and calling `Resque\Event::listen`. When running workers, if you run workers via the default `bin/resque` script, your `APP_INCLUDE` script should initialize and register any listeners required @@ -370,13 +370,13 @@ A sample plugin is included in the `extras` directory. #### beforeFirstFork #### -Called once, as a worker initializes. Argument passed is the instance of `Resque_Worker` +Called once, as a worker initializes. Argument passed is the instance of `Resque\Worker` that was just initialized. #### beforeFork #### Called before php-resque forks to run a job. Argument passed contains the instance of -`Resque_Job` for the job about to be run. +`Resque\Job` for the job about to be run. `beforeFork` is triggered in the **parent** process. Any changes made will be permanent for as long as the **worker** lives. @@ -384,7 +384,7 @@ for as long as the **worker** lives. #### afterFork #### Called after php-resque forks to run a job (but before the job is run). Argument -passed contains the instance of `Resque_Job` for the job about to be run. +passed contains the instance of `Resque\Job` for the job about to be run. `afterFork` is triggered in the **child** process after forking out to complete a job. Any changes made will only live as long as the **job** is being processed. @@ -392,16 +392,16 @@ changes made will only live as long as the **job** is being processed. #### beforePerform #### Called before the `setUp` and `perform` methods on a job are run. Argument passed -contains the instance of `Resque_Job` for the job about to be run. +contains the instance of `Resque\Job` for the job about to be run. -You can prevent execution of the job by throwing an exception of `Resque_Job_DontPerform`. +You can prevent execution of the job by throwing an exception of `Resque\Job\DontPerform`. Any other exceptions thrown will be treated as if they were thrown in a job, causing the job to fail. #### afterPerform #### Called after the `perform` and `tearDown` methods on a job are run. Argument passed -contains the instance of `Resque_Job` that was just run. +contains the instance of `Resque\Job` that was just run. Any exceptions thrown will be treated as if they were thrown in a job, causing the job to be marked as having failed. @@ -411,11 +411,11 @@ to be marked as having failed. Called whenever a job fails. Arguments passed (in this order) include: * Exception - The exception that was thrown when the job failed -* Resque_Job - The job that failed +* Resque\Job - The job that failed #### afterEnqueue #### -Called after a job has been queued using the `Resque::enqueue` method. Arguments passed +Called after a job has been queued using the `Resque\Resque::enqueue` method. Arguments passed (in this order) include: * Class - string containing the name of scheduled job diff --git a/bin/resque b/bin/resque index 4cb50da5..872c9cd9 100755 --- a/bin/resque +++ b/bin/resque @@ -2,6 +2,11 @@ 1) { $PREFIX = getenv('PREFIX'); if(!empty($PREFIX)) { $logger->log(Psr\Log\LogLevel::INFO, 'Prefix set to {prefix}', array('prefix' => $PREFIX)); - Resque_Redis::prefix($PREFIX); + Redis::prefix($PREFIX); } if($count > 1) { @@ -103,7 +108,7 @@ if($count > 1) { // Child, start the worker else if(!$pid) { $queues = explode(',', $QUEUE); - $worker = new Resque_Worker($queues); + $worker = new Worker($queues); $worker->setLogger($logger); $logger->log(Psr\Log\LogLevel::NOTICE, 'Starting worker {worker}', array('worker' => $worker)); $worker->work($interval, $BLOCKING); @@ -114,7 +119,7 @@ if($count > 1) { // Start a single worker else { $queues = explode(',', $QUEUE); - $worker = new Resque_Worker($queues); + $worker = new Worker($queues); $worker->setLogger($logger); $PIDFILE = getenv('PIDFILE'); @@ -126,4 +131,3 @@ else { $logger->log(Psr\Log\LogLevel::NOTICE, 'Starting worker {worker}', array('worker' => $worker)); $worker->work($interval, $BLOCKING); } -?> \ No newline at end of file diff --git a/composer.json b/composer.json index 6e31daac..ffa129d7 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "chrisboulton/php-resque", "type": "library", - "description": "Redis backed library for creating background jobs and processing them later. Based on resque for Ruby.", + "description": "Redis backed library for creating background jobs and processing them later. Based on Resque for Ruby.", "keywords": ["job", "background", "redis", "resque"], "homepage": "http://www.github.com/chrisboulton/php-resque/", "license": "MIT", @@ -9,7 +9,11 @@ { "name": "Chris Boulton", "email": "chris@bigcommerce.com" - } + }, + { + "name": "Jesse Decker", + "email": "jesse.decker@dmv.org" + } ], "repositories": [ { @@ -34,8 +38,8 @@ "bin/resque" ], "autoload": { - "psr-0": { - "Resque": "lib" + "psr-4": { + "Resque\\": "lib" } } } diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 1ecf21a5..00000000 --- a/composer.lock +++ /dev/null @@ -1,517 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "hash": "1862fac0c6f40ddf7d98bcef5f4bbd77", - "packages": [ - { - "name": "colinmollenhour/credis", - "version": "1.4", - "source": { - "type": "git", - "url": "https://github.com/colinmollenhour/credis.git", - "reference": "2079564c61cc49867eddc3cc918c711564164d65" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/2079564c61cc49867eddc3cc918c711564164d65", - "reference": "2079564c61cc49867eddc3cc918c711564164d65", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "Client.php", - "Cluster.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "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", - "time": "2014-05-05 19:31:30" - }, - { - "name": "psr/log", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-0": { - "Psr\\Log\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "time": "2012-12-21 11:40:51" - } - ], - "packages-dev": [ - { - "name": "phpunit/php-code-coverage", - "version": "1.2.17", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6ef2bf3a1c47eca07ea95f0d8a902a6340390b34" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6ef2bf3a1c47eca07ea95f0d8a902a6340390b34", - "reference": "6ef2bf3a1c47eca07ea95f0d8a902a6340390b34", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-file-iterator": ">=1.3.0@stable", - "phpunit/php-text-template": ">=1.2.0@stable", - "phpunit/php-token-stream": ">=1.1.3@stable" - }, - "require-dev": { - "phpunit/phpunit": "3.7.*@dev" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.0.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "PHP/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2014-03-28 10:53:45" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.3.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", - "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "File/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2013-10-10 15:34:57" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", - "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "Text/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2014-01-30 17:20:04" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", - "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "PHP/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2013-08-02 07:42:54" - }, - { - "name": "phpunit/php-token-stream", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32", - "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "classmap": [ - "PHP/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2014-03-03 05:10:30" - }, - { - "name": "phpunit/phpunit", - "version": "3.7.37", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ae6cefd7cc84586a5ef27e04bae11ee940ec63dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ae6cefd7cc84586a5ef27e04bae11ee940ec63dc", - "reference": "ae6cefd7cc84586a5ef27e04bae11ee940ec63dc", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=5.3.3", - "phpunit/php-code-coverage": "~1.2", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.1", - "phpunit/php-timer": "~1.0", - "phpunit/phpunit-mock-objects": "~1.2", - "symfony/yaml": "~2.0" - }, - "require-dev": { - "pear-pear.php.net/pear": "1.9.4" - }, - "suggest": { - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "composer/bin/phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.7.x-dev" - } - }, - "autoload": { - "classmap": [ - "PHPUnit/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "", - "../../symfony/yaml/" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "http://www.phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2014-04-30 12:24:19" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "1.2.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875", - "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-text-template": ">=1.1.1@stable" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "autoload": { - "classmap": [ - "PHPUnit/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2013-01-13 10:24:48" - }, - { - "name": "symfony/yaml", - "version": "v2.5.0", - "target-dir": "Symfony/Component/Yaml", - "source": { - "type": "git", - "url": "https://github.com/symfony/Yaml.git", - "reference": "b4b09c68ec2f2727574544ef0173684281a5033c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/b4b09c68ec2f2727574544ef0173684281a5033c", - "reference": "b4b09c68ec2f2727574544ef0173684281a5033c", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev" - } - }, - "autoload": { - "psr-0": { - "Symfony\\Component\\Yaml\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "http://symfony.com", - "time": "2014-05-16 14:25:18" - } - ], - "aliases": [ - - ], - "minimum-stability": "stable", - "stability-flags": [ - - ], - "platform": { - "php": ">=5.3.0", - "ext-pcntl": "*" - }, - "platform-dev": [ - - ] -} diff --git a/demo/check_status.php b/demo/check_status.php index 871dabab..608adc49 100644 --- a/demo/check_status.php +++ b/demo/check_status.php @@ -1,4 +1,7 @@ isTracking()) { die("Resque is not tracking the status of this job.\n"); } diff --git a/demo/queue.php b/demo/queue.php index 1eca1244..9801762c 100644 --- a/demo/queue.php +++ b/demo/queue.php @@ -1,4 +1,6 @@ time(), diff --git a/extras/sample-plugin.php b/extras/sample-plugin.php index e70dffde..362584d4 100644 --- a/extras/sample-plugin.php +++ b/extras/sample-plugin.php @@ -1,12 +1,14 @@ queues(false)) . "\n"; } - + public static function beforeFork($job) { echo "Just about to fork to run " . $job; } - + public static function afterFork($job) { echo "Forked to run " . $job . ". This is the child process.\n"; } - + public static function beforePerform($job) { echo "Cancelling " . $job . "\n"; - // throw new Resque_Job_DontPerform; + // throw new Resque\Job\DontPerform; } - + public static function afterPerform($job) { echo "Just performed " . $job . "\n"; } - + public static function onFailure($exception, $job) { echo $job . " threw an exception:\n" . $exception; diff --git a/lib/Event.php b/lib/Event.php new file mode 100644 index 00000000..8b8513bc --- /dev/null +++ b/lib/Event.php @@ -0,0 +1,90 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Event +{ + /** + * @var array Array containing all registered callbacks, indexked by event name. + */ + private static $events = array(); + + /** + * Raise a given event with the supplied data. + * + * @param string $event Name of event to be raised. + * @param mixed $data Optional, any data that should be passed to each callback. + * @return true + */ + public static function trigger($event, $data = null) + { + if (!is_array($data)) { + $data = array($data); + } + + if (empty(self::$events[$event])) { + return true; + } + + foreach (self::$events[$event] as $callback) { + if (!is_callable($callback)) { + continue; + } + call_user_func_array($callback, $data); + } + + return true; + } + + /** + * Listen in on a given event to have a specified callback fired. + * + * @param string $event Name of event to listen on. + * @param mixed $callback Any callback callable by call_user_func_array. + * @return true + */ + public static function listen($event, $callback) + { + if (!isset(self::$events[$event])) { + self::$events[$event] = array(); + } + + self::$events[$event][] = $callback; + return true; + } + + /** + * Stop a given callback from listening on a specific event. + * + * @param string $event Name of event. + * @param mixed $callback The callback as defined when listen() was called. + * @return true + */ + public static function stopListening($event, $callback) + { + if (!isset(self::$events[$event])) { + return true; + } + + $key = array_search($callback, self::$events[$event]); + if ($key !== false) { + unset(self::$events[$event][$key]); + } + + return true; + } + + /** + * Call all registered listeners. + */ + public static function clearListeners() + { + self::$events = array(); + } +} diff --git a/lib/Exception.php b/lib/Exception.php new file mode 100644 index 00000000..f779a5cf --- /dev/null +++ b/lib/Exception.php @@ -0,0 +1,13 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Exception extends \Exception +{ +} \ No newline at end of file diff --git a/lib/Failure.php b/lib/Failure.php new file mode 100644 index 00000000..507509c9 --- /dev/null +++ b/lib/Failure.php @@ -0,0 +1,59 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Failure +{ + /** + * @var string Class name representing the backend to pass failed jobs off to. + */ + private static $backend; + + /** + * Create a new failed job on the backend. + * + * @param object $payload The contents of the job that has just failed. + * @param \Exception $exception The exception generated when the job failed to run. + * @param \Resque\Worker $worker Instance of Resque_Worker that was running this job when it failed. + * @param string $queue The name of the queue that this job was fetched from. + */ + public static function create($payload, Exception $exception, Worker $worker, $queue) + { + $backend = self::getBackend(); + new $backend($payload, $exception, $worker, $queue); + } + + /** + * Return an instance of the backend for saving job failures. + * + * @return object Instance of backend object. + */ + public static function getBackend() + { + if (self::$backend === null) { + self::$backend = 'Resque\Failure\Resque_Failure_Redis'; + } + + return self::$backend; + } + + /** + * Set the backend to use for raised job failures. The supplied backend + * should be the name of a class to be instantiated when a job fails. + * It is your responsibility to have the backend class loaded (or autoloaded) + * + * @param string $backend The class name of the backend to pipe failures to. + */ + public static function setBackend($backend) + { + self::$backend = $backend; + } +} \ No newline at end of file diff --git a/lib/Failure/Redis.php b/lib/Failure/Redis.php new file mode 100644 index 00000000..e82b9a5b --- /dev/null +++ b/lib/Failure/Redis.php @@ -0,0 +1,38 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Redis implements Failure_Interface +{ + /** + * Initialize a failed job class and save it (where appropriate). + * + * @param object $payload Object containing details of the failed job. + * @param object $exception Instance of the exception that was thrown by the failed job. + * @param object $worker Instance of Resque\Worker that received the job. + * @param string $queue The name of the queue the job was fetched from. + */ + public function __construct($payload, $exception, $worker, $queue) + { + $data = new stdClass; + $data->failed_at = strftime('%a %b %d %H:%M:%S %Z %Y'); + $data->payload = $payload; + $data->exception = get_class($exception); + $data->error = $exception->getMessage(); + $data->backtrace = explode("\n", $exception->getTraceAsString()); + $data->worker = (string) $worker; + $data->queue = $queue; + $data = json_encode($data); + Resque::redis()->rpush('failed', $data); + } +} diff --git a/lib/Interface.php b/lib/Interface.php new file mode 100644 index 00000000..3e3afea3 --- /dev/null +++ b/lib/Interface.php @@ -0,0 +1,22 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +interface Failure_Interface +{ + /** + * Initialize a failed job class and save it (where appropriate). + * + * @param object $payload Object containing details of the failed job. + * @param object $exception Instance of the exception that was thrown by the failed job. + * @param object $worker Instance of Resque\Worker that received the job. + * @param string $queue The name of the queue the job was fetched from. + */ + public function __construct($payload, $exception, $worker, $queue); +} diff --git a/lib/Job.php b/lib/Job.php new file mode 100755 index 00000000..8ce65855 --- /dev/null +++ b/lib/Job.php @@ -0,0 +1,273 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Job +{ + /** + * @var string The name of the queue that this job belongs to. + */ + public $queue; + + /** + * @var Worker Instance of the Resque worker running this job. + */ + public $worker; + + /** + * @var object Object containing details of the job. + */ + public $payload; + + /** + * @var object Instance of the class performing work for this job. + */ + private $instance; + + /** + * Instantiate a new instance of a job. + * + * @param string $queue The queue that the job belongs to. + * @param array $payload array containing details of the job. + */ + public function __construct($queue, $payload) + { + $this->queue = $queue; + $this->payload = $payload; + } + + /** + * Create a new job and save it to the specified queue. + * + * @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. + * @param boolean $monitor Set to true to be able to monitor the status of a job. + * @return string + */ + public static function create($queue, $class, $args = null, $monitor = false) + { + if ($args !== null && !is_array($args)) { + throw new \InvalidArgumentException( + 'Supplied $args must be an array.' + ); + } + $id = md5(uniqid('', true)); + Resque::push($queue, array( + 'class' => $class, + 'args' => array($args), + 'id' => $id, + 'queue_time' => microtime(true), + )); + + if ($monitor) { + Job\Status::create($id); + } + + return $id; + } + + /** + * Find the next available job from the specified queue and return an + * instance of Resque\Job for it. + * + * @param string $queue The name of the queue to check for a job in. + * @return null|object Null when there aren't any waiting jobs, instance of Resque\Job when a job was found. + */ + public static function reserve($queue) + { + $payload = Resque::pop($queue); + if (!is_array($payload)) { + return false; + } + + return new Job($queue, $payload); + } + + /** + * Find the next available job from the specified queues using blocking list pop + * and return an instance of Resque\Job for it. + * + * @param array $queues + * @param int $timeout + * @return null|object Null when there aren't any waiting jobs, instance of Resque\Job when a job was found. + */ + public static function reserveBlocking(array $queues, $timeout = null) + { + $item = Resque::blpop($queues, $timeout); + + if (!is_array($item)) { + return false; + } + + return new Job($item['queue'], $item['payload']); + } + + /** + * Update the status of the current job. + * + * @param int $status Status constant from Resque\Job\Status indicating the current status of a job. + */ + public function updateStatus($status) + { + if (empty($this->payload['id'])) { + return; + } + + $statusInstance = new Job\Status($this->payload['id']); + $statusInstance->update($status); + } + + /** + * Return the status of the current job. + * + * @return int The status of the job as one of the Resque\Job\Status constants. + */ + public function getStatus() + { + $status = new Job\Status($this->payload['id']); + return $status->get(); + } + + /** + * Get the arguments supplied to this job. + * + * @return array Array of arguments. + */ + public function getArguments() + { + if (!isset($this->payload['args'])) { + return array(); + } + + return $this->payload['args'][0]; + } + + /** + * Get the instantiated object for this job that will be performing work. + * + * @return object Instance of the object that this job belongs to. + * @throws Exception + */ + public function getInstance() + { + if (!is_null($this->instance)) { + return $this->instance; + } + + if (!class_exists($this->payload['class'])) { + throw new Exception( + 'Could not find job class ' . $this->payload['class'] . '.' + ); + } + + if (!method_exists($this->payload['class'], 'perform')) { + throw new Exception( + 'Job class ' . $this->payload['class'] . ' does not contain a perform method.' + ); + } + + $this->instance = new $this->payload['class']; + $this->instance->job = $this; + $this->instance->args = $this->getArguments(); + $this->instance->queue = $this->queue; + return $this->instance; + } + + /** + * Actually execute a job by calling the perform method on the class + * associated with the job with the supplied arguments. + * + * @return bool + * @throws Exception When the job's class could not be found or it does not contain a perform method. + */ + public function perform() + { + try { + Event::trigger('beforePerform', $this); + + $instance = $this->getInstance(); + if (method_exists($instance, 'setUp')) { + $instance->setUp(); + } + + $instance->perform(); + + if (method_exists($instance, 'tearDown')) { + $instance->tearDown(); + } + + Event::trigger('afterPerform', $this); + } // beforePerform/setUp have said don't perform this job. Return. + catch (Job\DontPerform $e) { + return false; + } + + return true; + } + + /** + * Mark the current job as having failed. + * + * @param $exception + */ + public function fail($exception) + { + Event::trigger('onFailure', array( + 'exception' => $exception, + 'job' => $this, + )); + + $this->updateStatus(Job\Status::STATUS_FAILED); + Failure::create( + $this->payload, + $exception, + $this->worker, + $this->queue + ); + Stat::incr('failed'); + Stat::incr('failed:' . $this->worker); + } + + /** + * Re-queue the current job. + * + * @return string + */ + public function recreate() + { + $status = new Job\Status($this->payload['id']); + $monitor = false; + if ($status->isTracking()) { + $monitor = true; + } + + return self::create($this->queue, $this->payload['class'], $this->getArguments(), $monitor); + } + + /** + * Generate a string representation used to describe the current job. + * + * @return string The string representation of the job. + */ + public function __toString() + { + $name = array( + 'Job{' . $this->queue . '}' + ); + if (!empty($this->payload['id'])) { + $name[] = 'ID: ' . $this->payload['id']; + } + $name[] = $this->payload['class']; + if (!empty($this->payload['args'])) { + $name[] = json_encode($this->payload['args']); + } + return '(' . implode(' | ', $name) . ')'; + } +} diff --git a/lib/Job/DirtyExitException.php b/lib/Job/DirtyExitException.php new file mode 100644 index 00000000..94c7e68f --- /dev/null +++ b/lib/Job/DirtyExitException.php @@ -0,0 +1,16 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class DirtyExitException extends RuntimeException +{ + +} \ No newline at end of file diff --git a/lib/Job/DontPerform.php b/lib/Job/DontPerform.php new file mode 100644 index 00000000..73753e01 --- /dev/null +++ b/lib/Job/DontPerform.php @@ -0,0 +1,16 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class DontPerform extends Exception +{ + +} \ No newline at end of file diff --git a/lib/Job/Status.php b/lib/Job/Status.php new file mode 100644 index 00000000..d091a1b0 --- /dev/null +++ b/lib/Job/Status.php @@ -0,0 +1,146 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Status +{ + const STATUS_WAITING = 1; + const STATUS_RUNNING = 2; + const STATUS_FAILED = 3; + const STATUS_COMPLETE = 4; + + /** + * @var string The ID of the job this status class refers back to. + */ + private $id; + + /** + * @var mixed Cache variable if the status of this job is being monitored or not. + * True/false when checked at least once or null if not checked yet. + */ + private $isTracking = null; + + /** + * @var array Array of statuses that are considered final/complete. + */ + private static $completeStatuses = array( + self::STATUS_FAILED, + self::STATUS_COMPLETE + ); + + /** + * Setup a new instance of the job monitor class for the supplied job ID. + * + * @param string $id The ID of the job to manage the status for. + */ + public function __construct($id) + { + $this->id = $id; + } + + /** + * Create a new status monitor item for the supplied job ID. Will create + * all necessary keys in Redis to monitor the status of a job. + * + * @param string $id The ID of the job to monitor the status of. + */ + public static function create($id) + { + $statusPacket = array( + 'status' => self::STATUS_WAITING, + 'updated' => time(), + 'started' => time(), + ); + Resque::redis()->set('job:' . $id . ':status', json_encode($statusPacket)); + } + + /** + * Check if we're actually checking the status of the loaded job status + * instance. + * + * @return boolean True if the status is being monitored, false if not. + */ + public function isTracking() + { + if ($this->isTracking === false) { + return false; + } + + if (!Resque::redis()->exists((string) $this)) { + $this->isTracking = false; + return false; + } + + $this->isTracking = true; + return true; + } + + /** + * Update the status indicator for the current job with a new status. + * + * @param int $status The status of the job (see constants in Resque\Job\Status) + */ + public function update($status) + { + if (!$this->isTracking()) { + return; + } + + $statusPacket = array( + 'status' => $status, + 'updated' => time(), + ); + Resque::redis()->set((string) $this, json_encode($statusPacket)); + + // Expire the status for completed jobs after 24 hours + if (in_array($status, self::$completeStatuses)) { + Resque::redis()->expire((string) $this, 86400); + } + } + + /** + * Fetch the status for the job being monitored. + * + * @return mixed False if the status is not being monitored, otherwise the status as + * as an integer, based on the Resque\Job\Status constants. + */ + public function get() + { + if (!$this->isTracking()) { + return false; + } + + $statusPacket = json_decode(Resque::redis()->get((string) $this), true); + if (!$statusPacket) { + return false; + } + + return $statusPacket['status']; + } + + /** + * Stop tracking the status of a job. + */ + public function stop() + { + Resque::redis()->del((string) $this); + } + + /** + * Generate a string representation of this object. + * + * @return string String representation of the current job status class. + */ + public function __toString() + { + return 'job:' . $this->id . ':status'; + } +} diff --git a/lib/Log.php b/lib/Log.php new file mode 100644 index 00000000..98de103e --- /dev/null +++ b/lib/Log.php @@ -0,0 +1,68 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Log extends AbstractLogger +{ + public $verbose; + + public function __construct($verbose = false) + { + $this->verbose = $verbose; + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level PSR-3 log level constant, or equivalent string + * @param string $message Message to log, may contain a { placeholder } + * @param array $context Variables to replace { placeholder } + * @return null + */ + public function log($level, $message, array $context = array()) + { + if ($this->verbose) { + fwrite( + STDOUT, + '[' . $level . '] [' . strftime('%T %Y-%m-%d') . '] ' . $this->interpolate($message, $context) . PHP_EOL + ); + return; + } + + if (!($level === LogLevel::INFO || $level === LogLevel::DEBUG)) { + fwrite( + STDOUT, + '[' . $level . '] ' . $this->interpolate($message, $context) . PHP_EOL + ); + } + } + + /** + * Fill placeholders with the provided context + * + * @author Jordi Boggiano j.boggiano@seld.be + * @param string $message Message to be logged + * @param array $context Array of variables to use in message + * @return string + */ + public function interpolate($message, array $context = array()) + { + // build a replacement array with braces around the context keys + $replace = array(); + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + // interpolate replacement values into the message and return + return strtr($message, $replace); + } +} diff --git a/lib/Redis.php b/lib/Redis.php new file mode 100644 index 00000000..34d3c727 --- /dev/null +++ b/lib/Redis.php @@ -0,0 +1,293 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Redis +{ + /** + * Redis namespace + * + * @var string + */ + private static $defaultNamespace = 'resque:'; + + /** + * A default host to connect to + */ + const DEFAULT_HOST = 'localhost'; + + /** + * The default Redis port + */ + const DEFAULT_PORT = 6379; + + /** + * The default Redis Database number + */ + const DEFAULT_DATABASE = 0; + + /** + * @var array List of all commands in Redis that supply a key as their + * first argument. Used to prefix keys with the Resque\Resque namespace. + */ + private $keyCommands = array( + 'exists', + 'del', + 'type', + 'keys', + 'expire', + 'ttl', + 'move', + 'set', + 'setex', + 'get', + 'getset', + 'setnx', + 'incr', + 'incrby', + 'decr', + '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', + 'rename', + 'rpoplpush' + ); + // sinterstore + // sunion + // sunionstore + // sdiff + // sdiffstore + // sinter + // smove + // 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; + } + + /** + * @param string|array $server A DSN or array + * @param int $database A database number to select. However, if we find a valid database number in the DSN the + * DSN-supplied value will be used instead and this parameter is ignored. + */ + public function __construct($server, $database = null) + { + if (is_array($server)) { + $this->driver = new Credis_Cluster($server); + } else { + + list($host, $port, $dsnDatabase, $user, $password, $options) = self::parseDsn($server); + // $user is not used, only $password + + // Look for known Credis_Client options + $timeout = isset($options['timeout']) ? intval($options['timeout']) : null; + $persistent = isset($options['persistent']) ? $options['persistent'] : ''; + + $this->driver = new Credis_Client($host, $port, $timeout, $persistent); + if ($password) { + $this->driver->auth($password); + } + + // If we have found a database in our DSN, use it instead of the `$database` + // value passed into the constructor. + if ($dsnDatabase !== false) { + $database = $dsnDatabase; + } + } + + if ($database !== null) { + $this->driver->select($database); + } + } + + /** + * Parse a DSN string, which can have one of the following formats: + * - host:port + * - redis://user:pass@host:port/db?option1=val1&option2=val2 + * - tcp://user:pass@host:port/db?option1=val1&option2=val2 + * Note: the 'user' part of the DSN is not used. + * + * @param string $dsn A DSN string + * @return array An array of DSN compotnents, with 'false' values for any unknown components. e.g. + * [host, port, db, user, pass, options] + */ + public static function parseDsn($dsn) + { + if ($dsn == '') { + // Use a sensible default for an empty DNS string + $dsn = 'redis://' . self::DEFAULT_HOST; + } + $parts = parse_url($dsn); + + // Check the URI scheme + $validSchemes = array('redis', 'tcp'); + if (isset($parts['scheme']) && !in_array($parts['scheme'], $validSchemes)) { + throw new \InvalidArgumentException("Invalid DSN. Supported schemes are " . implode(', ', $validSchemes)); + } + + // Allow simple 'hostname' format, which `parse_url` treats as a path, not host. + if (!isset($parts['host']) && isset($parts['path'])) { + $parts['host'] = $parts['path']; + unset($parts['path']); + } + + // Extract the port number as an integer + $port = isset($parts['port']) ? intval($parts['port']) : self::DEFAULT_PORT; + + // Get the database from the 'path' part of the URI + $database = false; + if (isset($parts['path'])) { + // Strip non-digit chars from path + $database = intval(preg_replace('/[^0-9]/', '', $parts['path'])); + } + + // Extract any 'user' and 'pass' values + $user = isset($parts['user']) ? $parts['user'] : false; + $pass = isset($parts['pass']) ? $parts['pass'] : false; + + // Convert the query string into an associative array + $options = array(); + if (isset($parts['query'])) { + // Parse the query string into an array + parse_str($parts['query'], $options); + } + + return array( + $parts['host'], + $port, + $database, + $user, + $pass, + $options, + ); + } + + /** + * 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) + { + if (in_array($name, $this->keyCommands)) { + if (is_array($args[0])) { + foreach ($args[0] AS $i => $v) { + $args[0][$i] = self::$defaultNamespace . $v; + } + } else { + $args[0] = self::$defaultNamespace . $args[0]; + } + } + try { + return $this->driver->__call($name, $args); + } 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; + } +} diff --git a/lib/Resque.php b/lib/Resque.php index 6b067ae4..e4259369 100644 --- a/lib/Resque.php +++ b/lib/Resque.php @@ -1,345 +1,342 @@ - * @license http://www.opensource.org/licenses/mit-license.php + * @author Chris Boulton + * @license http://www.opensource.org/licenses/mit-license.php */ class Resque { - const VERSION = '1.2'; + const VERSION = '1.2'; const DEFAULT_INTERVAL = 5; - /** - * @var Resque_Redis Instance of Resque_Redis that talks to redis. - */ - public static $redis = null; - - /** - * @var mixed Host/port conbination separated by a colon, or a nested - * array of server swith host/port pairs - */ - protected static $redisServer = null; - - /** - * @var int ID of Redis database to select. - */ - protected static $redisDatabase = 0; - - /** - * Given a host/port combination separated by a colon, set it as - * the redis server that Resque will talk to. - * - * @param mixed $server Host/port combination separated by a colon, DSN-formatted URI, or - * a callable that receives the configured database ID - * and returns a Resque_Redis instance, or - * a nested array of servers with host/port pairs. - * @param int $database - */ - public static function setBackend($server, $database = 0) - { - self::$redisServer = $server; - self::$redisDatabase = $database; - self::$redis = null; - } - - /** - * Return an instance of the Resque_Redis class instantiated for Resque. - * - * @return Resque_Redis Instance of Resque_Redis. - */ - public static function redis() - { - if (self::$redis !== null) { - return self::$redis; - } - - if (is_callable(self::$redisServer)) { - self::$redis = call_user_func(self::$redisServer, self::$redisDatabase); - } else { - self::$redis = new Resque_Redis(self::$redisServer, 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; - } - - // 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; - } - - /** - * Push a job to the end of a specific queue. If the queue does not - * exist, then create it as well. - * - * @param string $queue The name of the queue to add the job to. - * @param array $item Job description as an array to be JSON encoded. - */ - public static function push($queue, $item) - { - self::redis()->sadd('queues', $queue); - $length = self::redis()->rpush('queue:' . $queue, json_encode($item)); - if ($length < 1) { - return false; - } - return true; - } - - /** - * Pop an item off the end of the specified queue, decode it and - * return it. - * - * @param string $queue The name of the queue to fetch an item from. - * @return array Decoded item from the queue. - */ - public static function pop($queue) - { + /** + * @var Redis Instance of Resque\Redis that talks to redis. + */ + public static $redis = null; + + /** + * @var mixed Host/port combination separated by a colon, or a nested + * array of servers with host/port pairs + */ + protected static $redisServer = null; + + /** + * @var int ID of Redis database to select. + */ + protected static $redisDatabase = 0; + + /** + * Given a host/port combination separated by a colon, set it as + * the redis server that Resque will talk to. + * + * @param mixed $server Host/port combination separated by a colon, DSN-formatted URI, or + * a callable that receives the configured database ID + * and returns a Resque\Redis instance, or + * a nested array of servers with host/port pairs. + * @param int $database + */ + public static function setBackend($server, $database = 0) + { + self::$redisServer = $server; + self::$redisDatabase = $database; + self::$redis = null; + } + + /** + * Return an instance of the Resque\Redis class instantiated for Resque. + * + * @return Redis Instance of Resque\Redis. + */ + public static function redis() + { + if (self::$redis !== null) { + return self::$redis; + } + + if (is_callable(self::$redisServer)) { + self::$redis = call_user_func(self::$redisServer, self::$redisDatabase); + } else { + self::$redis = new Redis(self::$redisServer, 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; + } + + // 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; + } + + /** + * Push a job to the end of a specific queue. If the queue does not + * exist, then create it as well. + * + * @param string $queue The name of the queue to add the job to. + * @param array $item Job description as an array to be JSON encoded. + * @return boolean + */ + public static function push($queue, $item) + { + self::redis()->sadd('queues', $queue); + $length = self::redis()->rpush('queue:' . $queue, json_encode($item)); + if ($length < 1) { + return false; + } + return true; + } + + /** + * Pop an item off the end of the specified queue, decode it and + * return it. + * + * @param string $queue The name of the queue to fetch an item from. + * @return array Decoded item from the queue. + */ + public static function pop($queue) + { $item = self::redis()->lpop('queue:' . $queue); - if(!$item) { - return; - } - - return json_decode($item, true); - } - - /** - * Remove items of the specified queue - * - * @param string $queue The name of the queue to fetch an item from. - * @param array $items - * @return integer number of deleted items - */ - public static function dequeue($queue, $items = Array()) - { - if(count($items) > 0) { - return self::removeItems($queue, $items); - } else { - return self::removeList($queue); - } - } - - /** - * Pop an item off the end of the specified queues, using blocking list pop, - * decode it and return it. - * - * @param array $queues - * @param int $timeout - * @return null|array Decoded item from the queue. - */ - public static function blpop(array $queues, $timeout) - { - $list = array(); - foreach($queues AS $queue) { - $list[] = 'queue:' . $queue; - } - - $item = self::redis()->blpop($list, (int)$timeout); - - if(!$item) { - return; - } - - /** - * Normally the Resque_Redis class returns queue names without the prefix - * But the blpop is a bit different. It returns the name as prefix:queue:name - * So we need to strip off the prefix:queue: part - */ - $queue = substr($item[0], strlen(self::redis()->getPrefix() . 'queue:')); - - return array( - 'queue' => $queue, - 'payload' => json_decode($item[1], true) - ); - } - - /** - * Return the size (number of pending jobs) of the specified queue. - * - * @param string $queue name of the queue to be checked for pending jobs - * - * @return int The size of the queue. - */ - public static function size($queue) - { - return self::redis()->llen('queue:' . $queue); - } - - /** - * Create a new job and save it to the specified queue. - * - * @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. - * @param boolean $trackStatus Set to true to be able to monitor the status of a job. - * - * @return string - */ - public static function enqueue($queue, $class, $args = null, $trackStatus = false) - { - $result = Resque_Job::create($queue, $class, $args, $trackStatus); - if ($result) { - Resque_Event::trigger('afterEnqueue', array( - 'class' => $class, - 'args' => $args, - 'queue' => $queue, - 'id' => $result, - )); - } - - return $result; - } - - /** - * Reserve and return the next available job in the specified queue. - * - * @param string $queue Queue to fetch next available job from. - * @return Resque_Job Instance of Resque_Job to be processed, false if none or error. - */ - public static function reserve($queue) - { - return Resque_Job::reserve($queue); - } - - /** - * Get an array of all known queues. - * - * @return array Array of queues. - */ - public static function queues() - { - $queues = self::redis()->smembers('queues'); - if(!is_array($queues)) { - $queues = array(); - } - return $queues; - } - - /** - * Remove Items from the queue - * Safely moving each item to a temporary queue before processing it - * If the Job matches, counts otherwise puts it in a requeue_queue - * which at the end eventually be copied back into the original queue - * - * @private - * - * @param string $queue The name of the queue - * @param array $items - * @return integer number of deleted items - */ - private static function removeItems($queue, $items = Array()) - { - $counter = 0; - $originalQueue = 'queue:'. $queue; - $tempQueue = $originalQueue. ':temp:'. time(); - $requeueQueue = $tempQueue. ':requeue'; - - // move each item from original queue to temp queue and process it - $finished = false; - while(!$finished) { - $string = self::redis()->rpoplpush($originalQueue, self::redis()->getPrefix() . $tempQueue); - - if(!empty($string)) { - if(self::matchItem($string, $items)) { - $counter++; - } else { - self::redis()->rpoplpush($tempQueue, self::redis()->getPrefix() . $requeueQueue); - } - } else { - $finished = true; - } - } - - // move back from temp queue to original queue - $finished = false; - while(!$finished) { - $string = self::redis()->rpoplpush($requeueQueue, self::redis()->getPrefix() .$originalQueue); - if (empty($string)) { - $finished = true; - } - } - - // remove temp queue and requeue queue - self::redis()->del($requeueQueue); - self::redis()->del($tempQueue); - - return $counter; - } - - /** - * matching item - * item can be ['class'] or ['class' => 'id'] or ['class' => {:foo => 1, :bar => 2}] - * @private - * - * @params string $string redis result in json - * @params $items - * - * @return (bool) - */ - private static function matchItem($string, $items) - { - $decoded = json_decode($string, true); - - foreach($items as $key => $val) { - # class name only ex: item[0] = ['class'] - if (is_numeric($key)) { - if($decoded['class'] == $val) { - return true; - } - # class name with args , example: item[0] = ['class' => {'foo' => 1, 'bar' => 2}] - } elseif (is_array($val)) { - $decodedArgs = (array)$decoded['args'][0]; - if ($decoded['class'] == $key && - count($decodedArgs) > 0 && count(array_diff($decodedArgs, $val)) == 0) { - return true; - } - # class name with ID, example: item[0] = ['class' => 'id'] - } else { - if ($decoded['class'] == $key && $decoded['id'] == $val) { - return true; - } - } - } - return false; - } - - /** - * Remove List - * - * @private - * - * @params string $queue the name of the queue - * @return integer number of deleted items belongs to this list - */ - private static function removeList($queue) - { - $counter = self::size($queue); - $result = self::redis()->del('queue:' . $queue); - return ($result == 1) ? $counter : 0; - } + if (!$item) { + return null; + } + + return json_decode($item, true); + } + + /** + * Remove items of the specified queue + * + * @param string $queue The name of the queue to fetch an item from. + * @param array $items + * @return integer number of deleted items + */ + public static function dequeue($queue, $items = Array()) + { + if (count($items) > 0) { + return self::removeItems($queue, $items); + } else { + return self::removeList($queue); + } + } + + /** + * Pop an item off the end of the specified queues, using blocking list pop, + * decode it and return it. + * + * @param array $queues + * @param int $timeout + * @return null|array Decoded item from the queue. + */ + public static function blpop(array $queues, $timeout) + { + $list = array(); + foreach ($queues AS $queue) { + $list[] = 'queue:' . $queue; + } + + $item = self::redis()->blpop($list, (int) $timeout); + + if (!$item) { + return null; + } + + /** + * Normally the Resque\Resque\Redis class returns queue names without the prefix + * But the blpop is a bit different. It returns the name as prefix:queue:name + * So we need to strip off the prefix:queue: part + */ + $queue = substr($item[0], strlen(self::redis()->getPrefix() . 'queue:')); + + return array( + 'queue' => $queue, + 'payload' => json_decode($item[1], true) + ); + } + + /** + * Return the size (number of pending jobs) of the specified queue. + * + * @param string $queue name of the queue to be checked for pending jobs + * @return int The size of the queue. + */ + public static function size($queue) + { + return self::redis()->llen('queue:' . $queue); + } + + /** + * Create a new job and save it to the specified queue. + * + * @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. + * @param boolean $trackStatus Set to true to be able to monitor the status of a job. + * @return string + */ + public static function enqueue($queue, $class, $args = null, $trackStatus = false) + { + $result = Job::create($queue, $class, $args, $trackStatus); + if ($result) { + Event::trigger('afterEnqueue', array( + 'class' => $class, + 'args' => $args, + 'queue' => $queue, + 'id' => $result, + )); + } + + return $result; + } + + /** + * Reserve and return the next available job in the specified queue. + * + * @param string $queue Queue to fetch next available job from. + * @return Job Resque\Job Instance of Resque\Job to be processed, false if none or error. + */ + public static function reserve($queue) + { + return Job::reserve($queue); + } + + /** + * Get an array of all known queues. + * + * @return array Array of queues. + */ + public static function queues() + { + $queues = self::redis()->smembers('queues'); + if (!is_array($queues)) { + $queues = array(); + } + return $queues; + } + + /** + * Remove Items from the queue + * Safely moving each item to a temporary queue before processing it + * If the Job matches, counts otherwise puts it in a requeue_queue + * which at the end eventually be copied back into the original queue + * + * @private + * @param string $queue The name of the queue + * @param array $items + * @return integer number of deleted items + */ + private static function removeItems($queue, $items = Array()) + { + $counter = 0; + $originalQueue = 'queue:' . $queue; + $tempQueue = $originalQueue . ':temp:' . time(); + $requeueQueue = $tempQueue . ':requeue'; + + // move each item from original queue to temp queue and process it + $finished = false; + while (!$finished) { + $string = self::redis()->rpoplpush($originalQueue, self::redis()->getPrefix() . $tempQueue); + + if (!empty($string)) { + if (self::matchItem($string, $items)) { + $counter++; + } else { + self::redis()->rpoplpush($tempQueue, self::redis()->getPrefix() . $requeueQueue); + } + } else { + $finished = true; + } + } + + // move back from temp queue to original queue + $finished = false; + while (!$finished) { + $string = self::redis()->rpoplpush($requeueQueue, self::redis()->getPrefix() . $originalQueue); + if (empty($string)) { + $finished = true; + } + } + + // remove temp queue and Resque queue + self::redis()->del($requeueQueue); + self::redis()->del($tempQueue); + + return $counter; + } + + /** + * matching item + * item can be ['class'] or ['class' => 'id'] or ['class' => {:foo => 1, :bar => 2}] + * + * @private + * @param string $string redis result in json + * @param $items + * @return bool + */ + private static function matchItem($string, $items) + { + $decoded = json_decode($string, true); + + foreach ($items as $key => $val) { + # class name only ex: item[0] = ['class'] + if (is_numeric($key)) { + if ($decoded['class'] == $val) { + return true; + } + # class name with args , example: item[0] = ['class' => {'foo' => 1, 'bar' => 2}] + } elseif (is_array($val)) { + $decodedArgs = (array) $decoded['args'][0]; + if ($decoded['class'] == $key && + count($decodedArgs) > 0 && count(array_diff($decodedArgs, $val)) == 0 + ) { + return true; + } + # class name with ID, example: item[0] = ['class' => 'id'] + } else { + if ($decoded['class'] == $key && $decoded['id'] == $val) { + return true; + } + } + } + return false; + } + + /** + * Remove List + * + * @private + * @param string $queue the name of the queue + * @return integer number of deleted items belongs to this list + */ + private static function removeList($queue) + { + $counter = self::size($queue); + $result = self::redis()->del('queue:' . $queue); + return ($result == 1) ? $counter : 0; + } } - diff --git a/lib/Resque/Event.php b/lib/Resque/Event.php deleted file mode 100644 index 20072ff9..00000000 --- a/lib/Resque/Event.php +++ /dev/null @@ -1,88 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Event -{ - /** - * @var array Array containing all registered callbacks, indexked by event name. - */ - private static $events = array(); - - /** - * Raise a given event with the supplied data. - * - * @param string $event Name of event to be raised. - * @param mixed $data Optional, any data that should be passed to each callback. - * @return true - */ - public static function trigger($event, $data = null) - { - if (!is_array($data)) { - $data = array($data); - } - - if (empty(self::$events[$event])) { - return true; - } - - foreach (self::$events[$event] as $callback) { - if (!is_callable($callback)) { - continue; - } - call_user_func_array($callback, $data); - } - - return true; - } - - /** - * Listen in on a given event to have a specified callback fired. - * - * @param string $event Name of event to listen on. - * @param mixed $callback Any callback callable by call_user_func_array. - * @return true - */ - public static function listen($event, $callback) - { - if (!isset(self::$events[$event])) { - self::$events[$event] = array(); - } - - self::$events[$event][] = $callback; - return true; - } - - /** - * Stop a given callback from listening on a specific event. - * - * @param string $event Name of event. - * @param mixed $callback The callback as defined when listen() was called. - * @return true - */ - public static function stopListening($event, $callback) - { - if (!isset(self::$events[$event])) { - return true; - } - - $key = array_search($callback, self::$events[$event]); - if ($key !== false) { - unset(self::$events[$event][$key]); - } - - return true; - } - - /** - * Call all registered listeners. - */ - public static function clearListeners() - { - self::$events = array(); - } -} diff --git a/lib/Resque/Exception.php b/lib/Resque/Exception.php deleted file mode 100644 index 60cca86f..00000000 --- a/lib/Resque/Exception.php +++ /dev/null @@ -1,12 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Exception extends Exception -{ -} -?> \ No newline at end of file diff --git a/lib/Resque/Failure.php b/lib/Resque/Failure.php deleted file mode 100644 index deb678f9..00000000 --- a/lib/Resque/Failure.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Failure -{ - /** - * @var string Class name representing the backend to pass failed jobs off to. - */ - private static $backend; - - /** - * Create a new failed job on the backend. - * - * @param object $payload The contents of the job that has just failed. - * @param \Exception $exception The exception generated when the job failed to run. - * @param \Resque_Worker $worker Instance of Resque_Worker that was running this job when it failed. - * @param string $queue The name of the queue that this job was fetched from. - */ - public static function create($payload, Exception $exception, Resque_Worker $worker, $queue) - { - $backend = self::getBackend(); - new $backend($payload, $exception, $worker, $queue); - } - - /** - * Return an instance of the backend for saving job failures. - * - * @return object Instance of backend object. - */ - public static function getBackend() - { - if(self::$backend === null) { - self::$backend = 'Resque_Failure_Redis'; - } - - return self::$backend; - } - - /** - * Set the backend to use for raised job failures. The supplied backend - * should be the name of a class to be instantiated when a job fails. - * It is your responsibility to have the backend class loaded (or autoloaded) - * - * @param string $backend The class name of the backend to pipe failures to. - */ - public static function setBackend($backend) - { - self::$backend = $backend; - } -} \ No newline at end of file diff --git a/lib/Resque/Failure/Interface.php b/lib/Resque/Failure/Interface.php deleted file mode 100644 index b7e5bc83..00000000 --- a/lib/Resque/Failure/Interface.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -interface Resque_Failure_Interface -{ - /** - * Initialize a failed job class and save it (where appropriate). - * - * @param object $payload Object containing details of the failed job. - * @param object $exception Instance of the exception that was thrown by the failed job. - * @param object $worker Instance of Resque_Worker that received the job. - * @param string $queue The name of the queue the job was fetched from. - */ - public function __construct($payload, $exception, $worker, $queue); -} -?> \ No newline at end of file diff --git a/lib/Resque/Failure/Redis.php b/lib/Resque/Failure/Redis.php deleted file mode 100644 index cfac5b6c..00000000 --- a/lib/Resque/Failure/Redis.php +++ /dev/null @@ -1,34 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ - -class Resque_Failure_Redis implements Resque_Failure_Interface -{ - /** - * Initialize a failed job class and save it (where appropriate). - * - * @param object $payload Object containing details of the failed job. - * @param object $exception Instance of the exception that was thrown by the failed job. - * @param object $worker Instance of Resque_Worker that received the job. - * @param string $queue The name of the queue the job was fetched from. - */ - public function __construct($payload, $exception, $worker, $queue) - { - $data = new stdClass; - $data->failed_at = strftime('%a %b %d %H:%M:%S %Z %Y'); - $data->payload = $payload; - $data->exception = get_class($exception); - $data->error = $exception->getMessage(); - $data->backtrace = explode("\n", $exception->getTraceAsString()); - $data->worker = (string)$worker; - $data->queue = $queue; - $data = json_encode($data); - Resque::redis()->rpush('failed', $data); - } -} -?> \ No newline at end of file diff --git a/lib/Resque/Job.php b/lib/Resque/Job.php deleted file mode 100755 index ca21445a..00000000 --- a/lib/Resque/Job.php +++ /dev/null @@ -1,272 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Job -{ - /** - * @var string The name of the queue that this job belongs to. - */ - public $queue; - - /** - * @var Resque_Worker Instance of the Resque worker running this job. - */ - public $worker; - - /** - * @var object Object containing details of the job. - */ - public $payload; - - /** - * @var object Instance of the class performing work for this job. - */ - private $instance; - - /** - * Instantiate a new instance of a job. - * - * @param string $queue The queue that the job belongs to. - * @param array $payload array containing details of the job. - */ - public function __construct($queue, $payload) - { - $this->queue = $queue; - $this->payload = $payload; - } - - /** - * Create a new job and save it to the specified queue. - * - * @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. - * @param boolean $monitor Set to true to be able to monitor the status of a job. - * - * @return string - */ - public static function create($queue, $class, $args = null, $monitor = false) - { - if($args !== null && !is_array($args)) { - throw new InvalidArgumentException( - 'Supplied $args must be an array.' - ); - } - $id = md5(uniqid('', true)); - Resque::push($queue, array( - 'class' => $class, - 'args' => array($args), - 'id' => $id, - 'queue_time' => microtime(true), - )); - - if($monitor) { - Resque_Job_Status::create($id); - } - - return $id; - } - - /** - * Find the next available job from the specified queue and return an - * instance of Resque_Job for it. - * - * @param string $queue The name of the queue to check for a job in. - * @return null|object Null when there aren't any waiting jobs, instance of Resque_Job when a job was found. - */ - public static function reserve($queue) - { - $payload = Resque::pop($queue); - if(!is_array($payload)) { - return false; - } - - return new Resque_Job($queue, $payload); - } - - /** - * Find the next available job from the specified queues using blocking list pop - * and return an instance of Resque_Job for it. - * - * @param array $queues - * @param int $timeout - * @return null|object Null when there aren't any waiting jobs, instance of Resque_Job when a job was found. - */ - public static function reserveBlocking(array $queues, $timeout = null) - { - $item = Resque::blpop($queues, $timeout); - - if(!is_array($item)) { - return false; - } - - return new Resque_Job($item['queue'], $item['payload']); - } - - /** - * Update the status of the current job. - * - * @param int $status Status constant from Resque_Job_Status indicating the current status of a job. - */ - public function updateStatus($status) - { - if(empty($this->payload['id'])) { - return; - } - - $statusInstance = new Resque_Job_Status($this->payload['id']); - $statusInstance->update($status); - } - - /** - * Return the status of the current job. - * - * @return int The status of the job as one of the Resque_Job_Status constants. - */ - public function getStatus() - { - $status = new Resque_Job_Status($this->payload['id']); - return $status->get(); - } - - /** - * Get the arguments supplied to this job. - * - * @return array Array of arguments. - */ - public function getArguments() - { - if (!isset($this->payload['args'])) { - return array(); - } - - return $this->payload['args'][0]; - } - - /** - * Get the instantiated object for this job that will be performing work. - * - * @return object Instance of the object that this job belongs to. - */ - public function getInstance() - { - if (!is_null($this->instance)) { - return $this->instance; - } - - if(!class_exists($this->payload['class'])) { - throw new Resque_Exception( - 'Could not find job class ' . $this->payload['class'] . '.' - ); - } - - if(!method_exists($this->payload['class'], 'perform')) { - throw new Resque_Exception( - 'Job class ' . $this->payload['class'] . ' does not contain a perform method.' - ); - } - - $this->instance = new $this->payload['class']; - $this->instance->job = $this; - $this->instance->args = $this->getArguments(); - $this->instance->queue = $this->queue; - return $this->instance; - } - - /** - * Actually execute a job by calling the perform method on the class - * associated with the job with the supplied arguments. - * - * @return bool - * @throws Resque_Exception When the job's class could not be found or it does not contain a perform method. - */ - public function perform() - { - try { - Resque_Event::trigger('beforePerform', $this); - - $instance = $this->getInstance(); - if(method_exists($instance, 'setUp')) { - $instance->setUp(); - } - - $instance->perform(); - - if(method_exists($instance, 'tearDown')) { - $instance->tearDown(); - } - - Resque_Event::trigger('afterPerform', $this); - } - // beforePerform/setUp have said don't perform this job. Return. - catch(Resque_Job_DontPerform $e) { - return false; - } - - return true; - } - - /** - * Mark the current job as having failed. - * - * @param $exception - */ - public function fail($exception) - { - Resque_Event::trigger('onFailure', array( - 'exception' => $exception, - 'job' => $this, - )); - - $this->updateStatus(Resque_Job_Status::STATUS_FAILED); - Resque_Failure::create( - $this->payload, - $exception, - $this->worker, - $this->queue - ); - Resque_Stat::incr('failed'); - Resque_Stat::incr('failed:' . $this->worker); - } - - /** - * Re-queue the current job. - * @return string - */ - public function recreate() - { - $status = new Resque_Job_Status($this->payload['id']); - $monitor = false; - if($status->isTracking()) { - $monitor = true; - } - - return self::create($this->queue, $this->payload['class'], $this->getArguments(), $monitor); - } - - /** - * Generate a string representation used to describe the current job. - * - * @return string The string representation of the job. - */ - public function __toString() - { - $name = array( - 'Job{' . $this->queue .'}' - ); - if(!empty($this->payload['id'])) { - $name[] = 'ID: ' . $this->payload['id']; - } - $name[] = $this->payload['class']; - if(!empty($this->payload['args'])) { - $name[] = json_encode($this->payload['args']); - } - return '(' . implode(' | ', $name) . ')'; - } -} -?> diff --git a/lib/Resque/Job/DirtyExitException.php b/lib/Resque/Job/DirtyExitException.php deleted file mode 100644 index 108e0613..00000000 --- a/lib/Resque/Job/DirtyExitException.php +++ /dev/null @@ -1,12 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Job_DirtyExitException extends RuntimeException -{ - -} \ No newline at end of file diff --git a/lib/Resque/Job/DontPerform.php b/lib/Resque/Job/DontPerform.php deleted file mode 100644 index 553327ff..00000000 --- a/lib/Resque/Job/DontPerform.php +++ /dev/null @@ -1,12 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Job_DontPerform extends Exception -{ - -} \ No newline at end of file diff --git a/lib/Resque/Job/Status.php b/lib/Resque/Job/Status.php deleted file mode 100644 index ffa351ba..00000000 --- a/lib/Resque/Job/Status.php +++ /dev/null @@ -1,143 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Job_Status -{ - const STATUS_WAITING = 1; - const STATUS_RUNNING = 2; - const STATUS_FAILED = 3; - const STATUS_COMPLETE = 4; - - /** - * @var string The ID of the job this status class refers back to. - */ - private $id; - - /** - * @var mixed Cache variable if the status of this job is being monitored or not. - * True/false when checked at least once or null if not checked yet. - */ - private $isTracking = null; - - /** - * @var array Array of statuses that are considered final/complete. - */ - private static $completeStatuses = array( - self::STATUS_FAILED, - self::STATUS_COMPLETE - ); - - /** - * Setup a new instance of the job monitor class for the supplied job ID. - * - * @param string $id The ID of the job to manage the status for. - */ - public function __construct($id) - { - $this->id = $id; - } - - /** - * Create a new status monitor item for the supplied job ID. Will create - * all necessary keys in Redis to monitor the status of a job. - * - * @param string $id The ID of the job to monitor the status of. - */ - public static function create($id) - { - $statusPacket = array( - 'status' => self::STATUS_WAITING, - 'updated' => time(), - 'started' => time(), - ); - Resque::redis()->set('job:' . $id . ':status', json_encode($statusPacket)); - } - - /** - * Check if we're actually checking the status of the loaded job status - * instance. - * - * @return boolean True if the status is being monitored, false if not. - */ - public function isTracking() - { - if($this->isTracking === false) { - return false; - } - - if(!Resque::redis()->exists((string)$this)) { - $this->isTracking = false; - return false; - } - - $this->isTracking = true; - return true; - } - - /** - * Update the status indicator for the current job with a new status. - * - * @param int The status of the job (see constants in Resque_Job_Status) - */ - public function update($status) - { - if(!$this->isTracking()) { - return; - } - - $statusPacket = array( - 'status' => $status, - 'updated' => time(), - ); - Resque::redis()->set((string)$this, json_encode($statusPacket)); - - // Expire the status for completed jobs after 24 hours - if(in_array($status, self::$completeStatuses)) { - Resque::redis()->expire((string)$this, 86400); - } - } - - /** - * Fetch the status for the job being monitored. - * - * @return mixed False if the status is not being monitored, otherwise the status as - * as an integer, based on the Resque_Job_Status constants. - */ - public function get() - { - if(!$this->isTracking()) { - return false; - } - - $statusPacket = json_decode(Resque::redis()->get((string)$this), true); - if(!$statusPacket) { - return false; - } - - return $statusPacket['status']; - } - - /** - * Stop tracking the status of a job. - */ - public function stop() - { - Resque::redis()->del((string)$this); - } - - /** - * Generate a string representation of this object. - * - * @return string String representation of the current job status class. - */ - public function __toString() - { - return 'job:' . $this->id . ':status'; - } -} -?> \ No newline at end of file diff --git a/lib/Resque/Log.php b/lib/Resque/Log.php deleted file mode 100644 index ce279cc6..00000000 --- a/lib/Resque/Log.php +++ /dev/null @@ -1,62 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Log extends Psr\Log\AbstractLogger -{ - public $verbose; - - public function __construct($verbose = false) { - $this->verbose = $verbose; - } - - /** - * Logs with an arbitrary level. - * - * @param mixed $level PSR-3 log level constant, or equivalent string - * @param string $message Message to log, may contain a { placeholder } - * @param array $context Variables to replace { placeholder } - * @return null - */ - public function log($level, $message, array $context = array()) - { - if ($this->verbose) { - fwrite( - STDOUT, - '[' . $level . '] [' . strftime('%T %Y-%m-%d') . '] ' . $this->interpolate($message, $context) . PHP_EOL - ); - return; - } - - if (!($level === Psr\Log\LogLevel::INFO || $level === Psr\Log\LogLevel::DEBUG)) { - fwrite( - STDOUT, - '[' . $level . '] ' . $this->interpolate($message, $context) . PHP_EOL - ); - } - } - - /** - * Fill placeholders with the provided context - * @author Jordi Boggiano j.boggiano@seld.be - * - * @param string $message Message to be logged - * @param array $context Array of variables to use in message - * @return string - */ - public function interpolate($message, array $context = array()) - { - // build a replacement array with braces around the context keys - $replace = array(); - foreach ($context as $key => $val) { - $replace['{' . $key . '}'] = $val; - } - - // interpolate replacement values into the message and return - return strtr($message, $replace); - } -} diff --git a/lib/Resque/Redis.php b/lib/Resque/Redis.php deleted file mode 100644 index 59ca215e..00000000 --- a/lib/Resque/Redis.php +++ /dev/null @@ -1,249 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Redis -{ - /** - * Redis namespace - * @var string - */ - private static $defaultNamespace = 'resque:'; - - /** - * A default host to connect to - */ - const DEFAULT_HOST = 'localhost'; - - /** - * The default Redis port - */ - const DEFAULT_PORT = 6379; - - /** - * The default Redis Database number - */ - const DEFAULT_DATABASE = 0; - - /** - * @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', - 'setex', - 'get', - 'getset', - 'setnx', - 'incr', - 'incrby', - 'decr', - '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', - 'rename', - 'rpoplpush' - ); - // sinterstore - // sunion - // sunionstore - // sdiff - // sdiffstore - // sinter - // smove - // 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; - } - - /** - * @param string|array $server A DSN or array - * @param int $database A database number to select. However, if we find a valid database number in the DSN the - * DSN-supplied value will be used instead and this parameter is ignored. - */ - public function __construct($server, $database = null) - { - if (is_array($server)) { - $this->driver = new Credis_Cluster($server); - } - else { - - list($host, $port, $dsnDatabase, $user, $password, $options) = self::parseDsn($server); - // $user is not used, only $password - - // Look for known Credis_Client options - $timeout = isset($options['timeout']) ? intval($options['timeout']) : null; - $persistent = isset($options['persistent']) ? $options['persistent'] : ''; - - $this->driver = new Credis_Client($host, $port, $timeout, $persistent); - if ($password){ - $this->driver->auth($password); - } - - // If we have found a database in our DSN, use it instead of the `$database` - // value passed into the constructor. - if ($dsnDatabase !== false) { - $database = $dsnDatabase; - } - } - - if ($database !== null) { - $this->driver->select($database); - } - } - - /** - * Parse a DSN string, which can have one of the following formats: - * - * - host:port - * - redis://user:pass@host:port/db?option1=val1&option2=val2 - * - tcp://user:pass@host:port/db?option1=val1&option2=val2 - * - * Note: the 'user' part of the DSN is not used. - * - * @param string $dsn A DSN string - * @return array An array of DSN compotnents, with 'false' values for any unknown components. e.g. - * [host, port, db, user, pass, options] - */ - public static function parseDsn($dsn) - { - if ($dsn == '') { - // Use a sensible default for an empty DNS string - $dsn = 'redis://' . self::DEFAULT_HOST; - } - $parts = parse_url($dsn); - - // Check the URI scheme - $validSchemes = array('redis', 'tcp'); - if (isset($parts['scheme']) && ! in_array($parts['scheme'], $validSchemes)) { - throw new \InvalidArgumentException("Invalid DSN. Supported schemes are " . implode(', ', $validSchemes)); - } - - // Allow simple 'hostname' format, which `parse_url` treats as a path, not host. - if ( ! isset($parts['host']) && isset($parts['path'])) { - $parts['host'] = $parts['path']; - unset($parts['path']); - } - - // Extract the port number as an integer - $port = isset($parts['port']) ? intval($parts['port']) : self::DEFAULT_PORT; - - // Get the database from the 'path' part of the URI - $database = false; - if (isset($parts['path'])) { - // Strip non-digit chars from path - $database = intval(preg_replace('/[^0-9]/', '', $parts['path'])); - } - - // Extract any 'user' and 'pass' values - $user = isset($parts['user']) ? $parts['user'] : false; - $pass = isset($parts['pass']) ? $parts['pass'] : false; - - // Convert the query string into an associative array - $options = array(); - if (isset($parts['query'])) { - // Parse the query string into an array - parse_str($parts['query'], $options); - } - - return array( - $parts['host'], - $port, - $database, - $user, - $pass, - $options, - ); - } - - /** - * 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) - { - if (in_array($name, $this->keyCommands)) { - if (is_array($args[0])) { - foreach ($args[0] AS $i => $v) { - $args[0][$i] = self::$defaultNamespace . $v; - } - } - else { - $args[0] = self::$defaultNamespace . $args[0]; - } - } - try { - return $this->driver->__call($name, $args); - } - 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; - } -} diff --git a/lib/Resque/Stat.php b/lib/Resque/Stat.php deleted file mode 100644 index bc00c636..00000000 --- a/lib/Resque/Stat.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Stat -{ - /** - * Get the value of the supplied statistic counter for the specified statistic. - * - * @param string $stat The name of the statistic to get the stats for. - * @return mixed Value of the statistic. - */ - public static function get($stat) - { - return (int)Resque::redis()->get('stat:' . $stat); - } - - /** - * Increment the value of the specified statistic by a certain amount (default is 1) - * - * @param string $stat The name of the statistic to increment. - * @param int $by The amount to increment the statistic by. - * @return boolean True if successful, false if not. - */ - public static function incr($stat, $by = 1) - { - return (bool)Resque::redis()->incrby('stat:' . $stat, $by); - } - - /** - * Decrement the value of the specified statistic by a certain amount (default is 1) - * - * @param string $stat The name of the statistic to decrement. - * @param int $by The amount to decrement the statistic by. - * @return boolean True if successful, false if not. - */ - public static function decr($stat, $by = 1) - { - return (bool)Resque::redis()->decrby('stat:' . $stat, $by); - } - - /** - * Delete a statistic with the given name. - * - * @param string $stat The name of the statistic to delete. - * @return boolean True if successful, false if not. - */ - public static function clear($stat) - { - return (bool)Resque::redis()->del('stat:' . $stat); - } -} \ No newline at end of file diff --git a/lib/Resque/Worker.php b/lib/Resque/Worker.php deleted file mode 100644 index 379b853e..00000000 --- a/lib/Resque/Worker.php +++ /dev/null @@ -1,567 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php - */ -class Resque_Worker -{ - /** - * @var LoggerInterface Logging object that impliments the PSR-3 LoggerInterface - */ - public $logger; - - /** - * @var array Array of all associated queues for this worker. - */ - private $queues = array(); - - /** - * @var string The hostname of this worker. - */ - private $hostname; - - /** - * @var boolean True if on the next iteration, the worker should shutdown. - */ - private $shutdown = false; - - /** - * @var boolean True if this worker is paused. - */ - private $paused = false; - - /** - * @var string String identifying this worker. - */ - private $id; - - /** - * @var Resque_Job Current job, if any, being processed by this worker. - */ - private $currentJob = null; - - /** - * @var int Process ID of child worker processes. - */ - private $child = null; - - /** - * Instantiate a new worker, given a list of queues that it should be working - * on. The list of queues should be supplied in the priority that they should - * be checked for jobs (first come, first served) - * - * Passing a single '*' allows the worker to work on all queues in alphabetical - * order. You can easily add new queues dynamically and have them worked on using - * this method. - * - * @param string|array $queues String with a single queue name, array with multiple. - */ - public function __construct($queues) - { - $this->logger = new Resque_Log(); - - if(!is_array($queues)) { - $queues = array($queues); - } - - $this->queues = $queues; - if(function_exists('gethostname')) { - $hostname = gethostname(); - } - else { - $hostname = php_uname('n'); - } - $this->hostname = $hostname; - $this->id = $this->hostname . ':'.getmypid() . ':' . implode(',', $this->queues); - } - - /** - * Return all workers known to Resque as instantiated instances. - * @return array - */ - public static function all() - { - $workers = Resque::redis()->smembers('workers'); - if(!is_array($workers)) { - $workers = array(); - } - - $instances = array(); - foreach($workers as $workerId) { - $instances[] = self::find($workerId); - } - return $instances; - } - - /** - * Given a worker ID, check if it is registered/valid. - * - * @param string $workerId ID of the worker. - * @return boolean True if the worker exists, false if not. - */ - public static function exists($workerId) - { - return (bool)Resque::redis()->sismember('workers', $workerId); - } - - /** - * Given a worker ID, find it and return an instantiated worker class for it. - * - * @param string $workerId The ID of the worker. - * @return Resque_Worker Instance of the worker. False if the worker does not exist. - */ - public static function find($workerId) - { - if(!self::exists($workerId) || false === strpos($workerId, ":")) { - return false; - } - - list($hostname, $pid, $queues) = explode(':', $workerId, 3); - $queues = explode(',', $queues); - $worker = new self($queues); - $worker->setId($workerId); - return $worker; - } - - /** - * Set the ID of this worker to a given ID string. - * - * @param string $workerId ID for the worker. - */ - public function setId($workerId) - { - $this->id = $workerId; - } - - /** - * The primary loop for a worker which when called on an instance starts - * the worker's life cycle. - * - * Queues are checked every $interval (seconds) for new jobs. - * - * @param int $interval How often to check for new jobs across the queues. - */ - public function work($interval = Resque::DEFAULT_INTERVAL, $blocking = false) - { - $this->updateProcLine('Starting'); - $this->startup(); - - while(true) { - if($this->shutdown) { - break; - } - - // Attempt to find and reserve a job - $job = false; - if(!$this->paused) { - if($blocking === true) { - $this->logger->log(Psr\Log\LogLevel::INFO, 'Starting blocking with timeout of {interval}', array('interval' => $interval)); - $this->updateProcLine('Waiting for ' . implode(',', $this->queues) . ' with blocking timeout ' . $interval); - } else { - $this->updateProcLine('Waiting for ' . implode(',', $this->queues) . ' with interval ' . $interval); - } - - $job = $this->reserve($blocking, $interval); - } - - if(!$job) { - // For an interval of 0, break now - helps with unit testing etc - if($interval == 0) { - break; - } - - if($blocking === false) - { - // If no job was found, we sleep for $interval before continuing and checking again - $this->logger->log(Psr\Log\LogLevel::INFO, 'Sleeping for {interval}', array('interval' => $interval)); - if($this->paused) { - $this->updateProcLine('Paused'); - } - else { - $this->updateProcLine('Waiting for ' . implode(',', $this->queues)); - } - - usleep($interval * 1000000); - } - - continue; - } - - $this->logger->log(Psr\Log\LogLevel::NOTICE, 'Starting work on {job}', array('job' => $job)); - Resque_Event::trigger('beforeFork', $job); - $this->workingOn($job); - - $this->child = Resque::fork(); - - // Forked and we're the child. Run the job. - if ($this->child === 0 || $this->child === false) { - $status = 'Processing ' . $job->queue . ' since ' . strftime('%F %T'); - $this->updateProcLine($status); - $this->logger->log(Psr\Log\LogLevel::INFO, $status); - $this->perform($job); - if ($this->child === 0) { - exit(0); - } - } - - if($this->child > 0) { - // Parent process, sit and wait - $status = 'Forked ' . $this->child . ' at ' . strftime('%F %T'); - $this->updateProcLine($status); - $this->logger->log(Psr\Log\LogLevel::INFO, $status); - - // Wait until the child process finishes before continuing - pcntl_wait($status); - $exitStatus = pcntl_wexitstatus($status); - if($exitStatus !== 0) { - $job->fail(new Resque_Job_DirtyExitException( - 'Job exited with exit code ' . $exitStatus - )); - } - } - - $this->child = null; - $this->doneWorking(); - } - - $this->unregisterWorker(); - } - - /** - * Process a single job. - * - * @param Resque_Job $job The job to be processed. - */ - public function perform(Resque_Job $job) - { - try { - Resque_Event::trigger('afterFork', $job); - $job->perform(); - } - catch(Exception $e) { - $this->logger->log(Psr\Log\LogLevel::CRITICAL, '{job} has failed {stack}', array('job' => $job, 'stack' => $e->getMessage())); - $job->fail($e); - return; - } - - $job->updateStatus(Resque_Job_Status::STATUS_COMPLETE); - $this->logger->log(Psr\Log\LogLevel::NOTICE, '{job} has finished', array('job' => $job)); - } - - /** - * @param bool $blocking - * @param int $timeout - * @return object|boolean Instance of Resque_Job if a job is found, false if not. - */ - public function reserve($blocking = false, $timeout = null) - { - $queues = $this->queues(); - if(!is_array($queues)) { - return; - } - - if($blocking === true) { - $job = Resque_Job::reserveBlocking($queues, $timeout); - if($job) { - $this->logger->log(Psr\Log\LogLevel::INFO, 'Found job on {queue}', array('queue' => $job->queue)); - return $job; - } - } else { - foreach($queues as $queue) { - $this->logger->log(Psr\Log\LogLevel::INFO, 'Checking {queue} for jobs', array('queue' => $queue)); - $job = Resque_Job::reserve($queue); - if($job) { - $this->logger->log(Psr\Log\LogLevel::INFO, 'Found job on {queue}', array('queue' => $job->queue)); - return $job; - } - } - } - - return false; - } - - /** - * Return an array containing all of the queues that this worker should use - * when searching for jobs. - * - * If * is found in the list of queues, every queue will be searched in - * alphabetic order. (@see $fetch) - * - * @param boolean $fetch If true, and the queue is set to *, will fetch - * all queue names from redis. - * @return array Array of associated queues. - */ - public function queues($fetch = true) - { - if(!in_array('*', $this->queues) || $fetch == false) { - return $this->queues; - } - - $queues = Resque::queues(); - sort($queues); - return $queues; - } - - /** - * Perform necessary actions to start a worker. - */ - private function startup() - { - $this->registerSigHandlers(); - $this->pruneDeadWorkers(); - Resque_Event::trigger('beforeFirstFork', $this); - $this->registerWorker(); - } - - /** - * On supported systems (with the PECL proctitle module installed), update - * the name of the currently running process to indicate the current state - * of a worker. - * - * @param string $status The updated process title. - */ - private function updateProcLine($status) - { - $processTitle = 'resque-' . Resque::VERSION . ': ' . $status; - if(function_exists('cli_set_process_title')) { - cli_set_process_title($processTitle); - } - else if(function_exists('setproctitle')) { - setproctitle($processTitle); - } - } - - /** - * Register signal handlers that a worker should respond to. - * - * TERM: Shutdown immediately and stop processing jobs. - * INT: Shutdown immediately and stop processing jobs. - * QUIT: Shutdown after the current job finishes processing. - * USR1: Kill the forked child immediately and continue processing jobs. - */ - private function registerSigHandlers() - { - if(!function_exists('pcntl_signal')) { - return; - } - - declare(ticks = 1); - pcntl_signal(SIGTERM, array($this, 'shutDownNow')); - pcntl_signal(SIGINT, array($this, 'shutDownNow')); - pcntl_signal(SIGQUIT, array($this, 'shutdown')); - pcntl_signal(SIGUSR1, array($this, 'killChild')); - pcntl_signal(SIGUSR2, array($this, 'pauseProcessing')); - pcntl_signal(SIGCONT, array($this, 'unPauseProcessing')); - $this->logger->log(Psr\Log\LogLevel::DEBUG, 'Registered signals'); - } - - /** - * Signal handler callback for USR2, pauses processing of new jobs. - */ - public function pauseProcessing() - { - $this->logger->log(Psr\Log\LogLevel::NOTICE, 'USR2 received; pausing job processing'); - $this->paused = true; - } - - /** - * Signal handler callback for CONT, resumes worker allowing it to pick - * up new jobs. - */ - public function unPauseProcessing() - { - $this->logger->log(Psr\Log\LogLevel::NOTICE, 'CONT received; resuming job processing'); - $this->paused = false; - } - - /** - * Schedule a worker for shutdown. Will finish processing the current job - * and when the timeout interval is reached, the worker will shut down. - */ - public function shutdown() - { - $this->shutdown = true; - $this->logger->log(Psr\Log\LogLevel::NOTICE, 'Shutting down'); - } - - /** - * Force an immediate shutdown of the worker, killing any child jobs - * currently running. - */ - public function shutdownNow() - { - $this->shutdown(); - $this->killChild(); - } - - /** - * Kill a forked child job immediately. The job it is processing will not - * be completed. - */ - public function killChild() - { - if(!$this->child) { - $this->logger->log(Psr\Log\LogLevel::DEBUG, 'No child to kill.'); - return; - } - - $this->logger->log(Psr\Log\LogLevel::INFO, 'Killing child at {child}', array('child' => $this->child)); - if(exec('ps -o pid,state -p ' . $this->child, $output, $returnCode) && $returnCode != 1) { - $this->logger->log(Psr\Log\LogLevel::DEBUG, 'Child {child} found, killing.', array('child' => $this->child)); - posix_kill($this->child, SIGKILL); - $this->child = null; - } - else { - $this->logger->log(Psr\Log\LogLevel::INFO, 'Child {child} not found, restarting.', array('child' => $this->child)); - $this->shutdown(); - } - } - - /** - * 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 Resque workers did not die gracefully - * and therefore leave state information in Redis. - */ - public function pruneDeadWorkers() - { - $workerPids = $this->workerPids(); - $workers = self::all(); - foreach($workers as $worker) { - if (is_object($worker)) { - list($host, $pid, $queues) = explode(':', (string)$worker, 3); - if($host != $this->hostname || in_array($pid, $workerPids) || $pid == getmypid()) { - continue; - } - $this->logger->log(Psr\Log\LogLevel::INFO, 'Pruning dead worker: {worker}', array('worker' => (string)$worker)); - $worker->unregisterWorker(); - } - } - } - - /** - * Return an array of process IDs for all of the Resque workers currently - * running on this machine. - * - * @return array Array of Resque worker process IDs. - */ - public function workerPids() - { - $pids = array(); - exec('ps -A -o pid,command | grep [r]esque', $cmdOutput); - foreach($cmdOutput as $line) { - list($pids[],) = explode(' ', trim($line), 2); - } - return $pids; - } - - /** - * Register this worker in Redis. - */ - public function registerWorker() - { - Resque::redis()->sadd('workers', (string)$this); - Resque::redis()->set('worker:' . (string)$this . ':started', strftime('%a %b %d %H:%M:%S %Z %Y')); - } - - /** - * Unregister this worker in Redis. (shutdown etc) - */ - public function unregisterWorker() - { - if(is_object($this->currentJob)) { - $this->currentJob->fail(new Resque_Job_DirtyExitException); - } - - $id = (string)$this; - Resque::redis()->srem('workers', $id); - Resque::redis()->del('worker:' . $id); - Resque::redis()->del('worker:' . $id . ':started'); - Resque_Stat::clear('processed:' . $id); - Resque_Stat::clear('failed:' . $id); - } - - /** - * Tell Redis which job we're currently working on. - * - * @param object $job Resque_Job instance containing the job we're working on. - */ - public function workingOn(Resque_Job $job) - { - $job->worker = $this; - $this->currentJob = $job; - $job->updateStatus(Resque_Job_Status::STATUS_RUNNING); - $data = json_encode(array( - 'queue' => $job->queue, - 'run_at' => strftime('%a %b %d %H:%M:%S %Z %Y'), - 'payload' => $job->payload - )); - Resque::redis()->set('worker:' . $job->worker, $data); - } - - /** - * Notify Redis that we've finished working on a job, clearing the working - * state and incrementing the job stats. - */ - public function doneWorking() - { - $this->currentJob = null; - Resque_Stat::incr('processed'); - Resque_Stat::incr('processed:' . (string)$this); - Resque::redis()->del('worker:' . (string)$this); - } - - /** - * Generate a string representation of this worker. - * - * @return string String identifier for this worker instance. - */ - public function __toString() - { - return $this->id; - } - - /** - * Return an object describing the job this worker is currently working on. - * - * @return object Object with details of current job. - */ - public function job() - { - $job = Resque::redis()->get('worker:' . $this); - if(!$job) { - return array(); - } - else { - return json_decode($job, true); - } - } - - /** - * Get a statistic belonging to this worker. - * - * @param string $stat Statistic to fetch. - * @return int Statistic value. - */ - public function getStat($stat) - { - return Resque_Stat::get($stat . ':' . $this); - } - - /** - * Inject the logging object into the worker - * - * @param Psr\Log\LoggerInterface $logger - */ - public function setLogger(Psr\Log\LoggerInterface $logger) - { - $this->logger = $logger; - } -} -?> diff --git a/lib/Stat.php b/lib/Stat.php new file mode 100644 index 00000000..bbb15364 --- /dev/null +++ b/lib/Stat.php @@ -0,0 +1,58 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Stat +{ + /** + * Get the value of the supplied statistic counter for the specified statistic. + * + * @param string $stat The name of the statistic to get the stats for. + * @return mixed Value of the statistic. + */ + public static function get($stat) + { + return (int) Resque::redis()->get('stat:' . $stat); + } + + /** + * Increment the value of the specified statistic by a certain amount (default is 1) + * + * @param string $stat The name of the statistic to increment. + * @param int $by The amount to increment the statistic by. + * @return boolean True if successful, false if not. + */ + public static function incr($stat, $by = 1) + { + return (bool) Resque::redis()->incrby('stat:' . $stat, $by); + } + + /** + * Decrement the value of the specified statistic by a certain amount (default is 1) + * + * @param string $stat The name of the statistic to decrement. + * @param int $by The amount to decrement the statistic by. + * @return boolean True if successful, false if not. + */ + public static function decr($stat, $by = 1) + { + return (bool) Resque::redis()->decrby('stat:' . $stat, $by); + } + + /** + * Delete a statistic with the given name. + * + * @param string $stat The name of the statistic to delete. + * @return boolean True if successful, false if not. + */ + public static function clear($stat) + { + return (bool) Resque::redis()->del('stat:' . $stat); + } +} \ No newline at end of file diff --git a/lib/Worker.php b/lib/Worker.php new file mode 100644 index 00000000..c9a866a3 --- /dev/null +++ b/lib/Worker.php @@ -0,0 +1,564 @@ + + * @license http://www.opensource.org/licenses/mit-license.php + */ +class Worker +{ + /** + * @var LoggerInterface Logging object that implements the PSR-3 LoggerInterface + */ + public $logger; + + /** + * @var array Array of all associated queues for this worker. + */ + private $queues = array(); + + /** + * @var string The hostname of this worker. + */ + private $hostname; + + /** + * @var boolean True if on the next iteration, the worker should shutdown. + */ + private $shutdown = false; + + /** + * @var boolean True if this worker is paused. + */ + private $paused = false; + + /** + * @var string String identifying this worker. + */ + private $id; + + /** + * @var Job Current job, if any, being processed by this worker. + */ + private $currentJob = null; + + /** + * @var int Process ID of child worker processes. + */ + private $child = null; + + /** + * Instantiate a new worker, given a list of queues that it should be working + * on. The list of queues should be supplied in the priority that they should + * be checked for jobs (first come, first served) + * Passing a single '*' allows the worker to work on all queues in alphabetical + * order. You can easily add new queues dynamically and have them worked on using + * this method. + * + * @param string|array $queues String with a single queue name, array with multiple. + */ + public function __construct($queues) + { + $this->logger = new Log(); + + if (!is_array($queues)) { + $queues = array($queues); + } + + $this->queues = $queues; + if (function_exists('gethostname')) { + $hostname = gethostname(); + } else { + $hostname = php_uname('n'); + } + $this->hostname = $hostname; + $this->id = $this->hostname . ':' . getmypid() . ':' . implode(',', $this->queues); + } + + /** + * Return all workers known to Resque as instantiated instances. + * + * @return Worker[] + */ + public static function all() + { + $workers = Resque::redis()->smembers('workers'); + if (!is_array($workers)) { + $workers = array(); + } + + $instances = array(); + foreach ($workers as $workerId) { + $instances[] = self::find($workerId); + } + return $instances; + } + + /** + * Given a worker ID, check if it is registered/valid. + * + * @param string $workerId ID of the worker. + * @return boolean True if the worker exists, false if not. + */ + public static function exists($workerId) + { + return (bool) Resque::redis()->sismember('workers', $workerId); + } + + /** + * Given a worker ID, find it and return an instantiated worker class for it. + + * +*@param string $workerId The ID of the worker. + * @return Worker Instance of the worker. False if the worker does not exist. + */ + public static function find($workerId) + { + if (!self::exists($workerId) || false === strpos($workerId, ":")) { + return false; + } + + list($hostname, $pid, $queues) = explode(':', $workerId, 3); + $queues = explode(',', $queues); + $worker = new self($queues); + $worker->setId($workerId); + return $worker; + } + + /** + * Set the ID of this worker to a given ID string. + * + * @param string $workerId ID for the worker. + */ + public function setId($workerId) + { + $this->id = $workerId; + } + + /** + * The primary loop for a worker which when called on an instance starts + * the worker's life cycle. + * Queues are checked every $interval (seconds) for new jobs. + * + * @param int $interval How often to check for new jobs across the queues. + * @param bool $blocking + */ + public function work($interval = Resque::DEFAULT_INTERVAL, $blocking = false) + { + $this->updateProcLine('Starting'); + $this->startup(); + + while (true) { + if ($this->shutdown) { + break; + } + + // Attempt to find and reserve a job + $job = false; + if (!$this->paused) { + if ($blocking === true) { + $this->logger->log(LogLevel::INFO, 'Starting blocking with timeout of {interval}', array('interval' => $interval)); + $this->updateProcLine('Waiting for ' . implode(',', $this->queues) . ' with blocking timeout ' . $interval); + } else { + $this->updateProcLine('Waiting for ' . implode(',', $this->queues) . ' with interval ' . $interval); + } + + $job = $this->reserve($blocking, $interval); + } + + if (!$job) { + // For an interval of 0, break now - helps with unit testing etc + if ($interval == 0) { + break; + } + + if ($blocking === false) { + // If no job was found, we sleep for $interval before continuing and checking again + $this->logger->log(LogLevel::INFO, 'Sleeping for {interval}', array('interval' => $interval)); + if ($this->paused) { + $this->updateProcLine('Paused'); + } else { + $this->updateProcLine('Waiting for ' . implode(',', $this->queues)); + } + + usleep($interval * 1000000); + } + + continue; + } + + $this->logger->log(LogLevel::NOTICE, 'Starting work on {job}', array('job' => $job)); + Event::trigger('beforeFork', $job); + $this->workingOn($job); + + $this->child = Resque::fork(); + + // Forked and we're the child. Run the job. + if ($this->child === 0 || $this->child === false) { + $status = 'Processing ' . $job->queue . ' since ' . strftime('%F %T'); + $this->updateProcLine($status); + $this->logger->log(LogLevel::INFO, $status); + $this->perform($job); + if ($this->child === 0) { + exit(0); + } + } + + if ($this->child > 0) { + // Parent process, sit and wait + $status = 'Forked ' . $this->child . ' at ' . strftime('%F %T'); + $this->updateProcLine($status); + $this->logger->log(LogLevel::INFO, $status); + + // Wait until the child process finishes before continuing + pcntl_wait($status); + $exitStatus = pcntl_wexitstatus($status); + if ($exitStatus !== 0) { + $job->fail(new DirtyExitException( + 'Job exited with exit code ' . $exitStatus + )); + } + } + + $this->child = null; + $this->doneWorking(); + } + + $this->unregisterWorker(); + } + + /** + * Process a single job. + * + * @param Job $job The job to be processed. + */ + public function perform(Job $job) + { + try { + Event::trigger('afterFork', $job); + $job->perform(); + } catch (Exception $e) { + $this->logger->log(LogLevel::CRITICAL, '{job} has failed {stack}', array('job' => $job, 'stack' => $e->getMessage())); + $job->fail($e); + return; + } + + $job->updateStatus(Status::STATUS_COMPLETE); + $this->logger->log(LogLevel::NOTICE, '{job} has finished', array('job' => $job)); + } + + /** + * @param bool $blocking + * @param int $timeout + * @return Job|boolean Instance of Resque\Job if a job is found, false if not. + */ + public function reserve($blocking = false, $timeout = null) + { + $queues = $this->queues(); + if (!is_array($queues)) { + return false; + } + + if ($blocking === true) { + $job = Job::reserveBlocking($queues, $timeout); + if ($job) { + $this->logger->log(LogLevel::INFO, 'Found job on {queue}', array('queue' => $job->queue)); + return $job; + } + } else { + foreach ($queues as $queue) { + $this->logger->log(LogLevel::INFO, 'Checking {queue} for jobs', array('queue' => $queue)); + $job = Job::reserve($queue); + if ($job) { + $this->logger->log(LogLevel::INFO, 'Found job on {queue}', array('queue' => $job->queue)); + return $job; + } + } + } + + return false; + } + + /** + * Return an array containing all of the queues that this worker should use + * when searching for jobs. + * If * is found in the list of queues, every queue will be searched in + * alphabetic order. (@see $fetch) + * + * @param boolean $fetch If true, and the queue is set to *, will fetch + * all queue names from redis. + * @return array Array of associated queues. + */ + public function queues($fetch = true) + { + if (!in_array('*', $this->queues) || $fetch == false) { + return $this->queues; + } + + $queues = Resque::queues(); + sort($queues); + return $queues; + } + + /** + * Perform necessary actions to start a worker. + */ + private function startup() + { + $this->registerSigHandlers(); + $this->pruneDeadWorkers(); + Event::trigger('beforeFirstFork', $this); + $this->registerWorker(); + } + + /** + * On supported systems (with the PECL proctitle module installed), update + * the name of the currently running process to indicate the current state + * of a worker. + * + * @param string $status The updated process title. + */ + private function updateProcLine($status) + { + $processTitle = 'resque-' . Resque::VERSION . ': ' . $status; + if (function_exists('cli_set_process_title')) { + cli_set_process_title($processTitle); + } else if (function_exists('setproctitle')) { + setproctitle($processTitle); + } + } + + /** + * Register signal handlers that a worker should respond to. + * TERM: Shutdown immediately and stop processing jobs. + * INT: Shutdown immediately and stop processing jobs. + * QUIT: Shutdown after the current job finishes processing. + * USR1: Kill the forked child immediately and continue processing jobs. + */ + private function registerSigHandlers() + { + if (!function_exists('pcntl_signal')) { + return; + } + + declare(ticks = 1); + pcntl_signal(SIGTERM, array($this, 'shutDownNow')); + pcntl_signal(SIGINT, array($this, 'shutDownNow')); + pcntl_signal(SIGQUIT, array($this, 'shutdown')); + pcntl_signal(SIGUSR1, array($this, 'killChild')); + pcntl_signal(SIGUSR2, array($this, 'pauseProcessing')); + pcntl_signal(SIGCONT, array($this, 'unPauseProcessing')); + $this->logger->log(LogLevel::DEBUG, 'Registered signals'); + } + + /** + * Signal handler callback for USR2, pauses processing of new jobs. + */ + public function pauseProcessing() + { + $this->logger->log(LogLevel::NOTICE, 'USR2 received; pausing job processing'); + $this->paused = true; + } + + /** + * Signal handler callback for CONT, resumes worker allowing it to pick + * up new jobs. + */ + public function unPauseProcessing() + { + $this->logger->log(LogLevel::NOTICE, 'CONT received; resuming job processing'); + $this->paused = false; + } + + /** + * Schedule a worker for shutdown. Will finish processing the current job + * and when the timeout interval is reached, the worker will shut down. + */ + public function shutdown() + { + $this->shutdown = true; + $this->logger->log(LogLevel::NOTICE, 'Shutting down'); + } + + /** + * Force an immediate shutdown of the worker, killing any child jobs + * currently running. + */ + public function shutdownNow() + { + $this->shutdown(); + $this->killChild(); + } + + /** + * Kill a forked child job immediately. The job it is processing will not + * be completed. + */ + public function killChild() + { + if (!$this->child) { + $this->logger->log(LogLevel::DEBUG, 'No child to kill.'); + return; + } + + $this->logger->log(LogLevel::INFO, 'Killing child at {child}', array('child' => $this->child)); + if (exec('ps -o pid,state -p ' . $this->child, $output, $returnCode) && $returnCode != 1) { + $this->logger->log(LogLevel::DEBUG, 'Child {child} found, killing.', array('child' => $this->child)); + posix_kill($this->child, SIGKILL); + $this->child = null; + } else { + $this->logger->log(LogLevel::INFO, 'Child {child} not found, restarting.', array('child' => $this->child)); + $this->shutdown(); + } + } + + /** + * 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 Resque workers did not die gracefully + * and therefore leave state information in Redis. + */ + public function pruneDeadWorkers() + { + $workerPids = $this->workerPids(); + $workers = self::all(); + foreach ($workers as $worker) { + if (is_object($worker)) { + list($host, $pid, $queues) = explode(':', (string) $worker, 3); + if ($host != $this->hostname || in_array($pid, $workerPids) || $pid == getmypid()) { + continue; + } + $this->logger->log(LogLevel::INFO, 'Pruning dead worker: {worker}', array('worker' => (string) $worker)); + $worker->unregisterWorker(); + } + } + } + + /** + * Return an array of process IDs for all of the Resque workers currently + * running on this machine. + * + * @return array Array of Resque worker process IDs. + */ + public function workerPids() + { + $pids = array(); + exec('ps -A -o pid,command | grep [r]esque', $cmdOutput); + foreach ($cmdOutput as $line) { + list($pids[],) = explode(' ', trim($line), 2); + } + return $pids; + } + + /** + * Register this worker in Redis. + */ + public function registerWorker() + { + Resque::redis()->sadd('workers', (string) $this); + Resque::redis()->set('worker:' . (string) $this . ':started', strftime('%a %b %d %H:%M:%S %Z %Y')); + } + + /** + * Unregister this worker in Redis. (shutdown etc) + */ + public function unregisterWorker() + { + if (is_object($this->currentJob)) { + $this->currentJob->fail(new DirtyExitException); + } + + $id = (string) $this; + Resque::redis()->srem('workers', $id); + Resque::redis()->del('worker:' . $id); + Resque::redis()->del('worker:' . $id . ':started'); + Stat::clear('processed:' . $id); + Stat::clear('failed:' . $id); + } + + /** + * Tell Redis which job we're currently working on. + * + * @param Job $job Job instance containing the job we're working on. + */ + public function workingOn(Job $job) + { + $job->worker = $this; + $this->currentJob = $job; + $job->updateStatus(Status::STATUS_RUNNING); + $data = json_encode(array( + 'queue' => $job->queue, + 'run_at' => strftime('%a %b %d %H:%M:%S %Z %Y'), + 'payload' => $job->payload + )); + Resque::redis()->set('worker:' . $job->worker, $data); + } + + /** + * Notify Redis that we've finished working on a job, clearing the working + * state and incrementing the job stats. + */ + public function doneWorking() + { + $this->currentJob = null; + Stat::incr('processed'); + Stat::incr('processed:' . (string) $this); + Resque::redis()->del('worker:' . (string) $this); + } + + /** + * Generate a string representation of this worker. + * + * @return string String identifier for this worker instance. + */ + public function __toString() + { + return $this->id; + } + + /** + * Return an object describing the job this worker is currently working on. + * + * @return object Object with details of current job. + */ + public function job() + { + $job = Resque::redis()->get('worker:' . $this); + if (!$job) { + return array(); + } else { + return json_decode($job, true); + } + } + + /** + * Get a statistic belonging to this worker. + * + * @param string $stat Statistic to fetch. + * @return int Statistic value. + */ + public function getStat($stat) + { + return Stat::get($stat . ':' . $this); + } + + /** + * Inject the logging object into the worker + * + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } +} diff --git a/test/Resque/Tests/DsnTest.php b/test/Resque/Tests/DsnTest.php index 086db1ea..aa02c8f5 100644 --- a/test/Resque/Tests/DsnTest.php +++ b/test/Resque/Tests/DsnTest.php @@ -1,6 +1,8 @@ @@ -20,14 +22,14 @@ public function validDsnStringProvider() // Input , Expected output array('', array( 'localhost', - Resque_Redis::DEFAULT_PORT, + Redis::DEFAULT_PORT, false, false, false, array(), )), array('localhost', array( 'localhost', - Resque_Redis::DEFAULT_PORT, + Redis::DEFAULT_PORT, false, false, false, array(), @@ -48,14 +50,14 @@ public function validDsnStringProvider() )), array('redis://foobar', array( 'foobar', - Resque_Redis::DEFAULT_PORT, + Redis::DEFAULT_PORT, false, false, false, array(), )), array('redis://foobar/', array( 'foobar', - Resque_Redis::DEFAULT_PORT, + Redis::DEFAULT_PORT, false, false, false, array(), @@ -165,7 +167,7 @@ public function bogusDsnStringProvider() */ public function testParsingValidDsnString($dsn, $expected) { - $result = Resque_Redis::parseDsn($dsn); + $result = Redis::parseDsn($dsn); $this->assertEquals($expected, $result); } @@ -176,7 +178,7 @@ public function testParsingValidDsnString($dsn, $expected) public function testParsingBogusDsnStringThrowsException($dsn) { // The next line should throw an InvalidArgumentException - $result = Resque_Redis::parseDsn($dsn); + $result = Redis::parseDsn($dsn); } } diff --git a/test/Resque/Tests/EventTest.php b/test/Resque/Tests/EventTest.php index 894fd7a9..7fd3a4b1 100644 --- a/test/Resque/Tests/EventTest.php +++ b/test/Resque/Tests/EventTest.php @@ -1,6 +1,13 @@ @@ -9,20 +16,22 @@ class Resque_Tests_EventTest extends Resque_Tests_TestCase { private $callbacksHit = array(); - + /** @var Worker */ + private $worker; + public function setUp() { Test_Job::$called = false; - + // Register a worker to test with - $this->worker = new Resque_Worker('jobs'); - $this->worker->setLogger(new Resque_Log()); + $this->worker = new Worker('jobs'); + $this->worker->setLogger(new Log()); $this->worker->registerWorker(); } public function tearDown() { - Resque_Event::clearListeners(); + Event::clearListeners(); $this->callbacksHit = array(); } @@ -34,11 +43,11 @@ public function getEventTestJob() 'somevar', ), ); - $job = new Resque_Job('jobs', $payload); + $job = new Job('jobs', $payload); $job->worker = $this->worker; return $job; } - + public function eventCallbackProvider() { return array( @@ -47,27 +56,27 @@ public function eventCallbackProvider() array('afterFork', 'afterForkEventCallback'), ); } - + /** * @dataProvider eventCallbackProvider */ public function testEventCallbacksFire($event, $callback) { - Resque_Event::listen($event, array($this, $callback)); + Event::listen($event, array($this, $callback)); $job = $this->getEventTestJob(); $this->worker->perform($job); $this->worker->work(0); - + $this->assertContains($callback, $this->callbacksHit, $event . ' callback (' . $callback .') was not called'); } - + public function testBeforeForkEventCallbackFires() { $event = 'beforeFork'; $callback = 'beforeForkEventCallback'; - Resque_Event::listen($event, array($this, $callback)); + Event::listen($event, array($this, $callback)); Resque::enqueue('jobs', 'Test_Job', array( 'somevar' )); @@ -79,24 +88,24 @@ public function testBeforeForkEventCallbackFires() public function testBeforePerformEventCanStopWork() { $callback = 'beforePerformEventDontPerformCallback'; - Resque_Event::listen('beforePerform', array($this, $callback)); + Event::listen('beforePerform', array($this, $callback)); $job = $this->getEventTestJob(); $this->assertFalse($job->perform()); $this->assertContains($callback, $this->callbacksHit, $callback . ' callback was not called'); - $this->assertFalse(Test_Job::$called, 'Job was still performed though Resque_Job_DontPerform was thrown'); + $this->assertFalse(Test_Job::$called, 'Job was still performed though Resque\Job\Resque_Job_DontPerform was thrown'); } - + public function testAfterEnqueueEventCallbackFires() { $callback = 'afterEnqueueEventCallback'; $event = 'afterEnqueue'; - - Resque_Event::listen($event, array($this, $callback)); + + Event::listen($event, array($this, $callback)); Resque::enqueue('jobs', 'Test_Job', array( 'somevar' - )); + )); $this->assertContains($callback, $this->callbacksHit, $event . ' callback (' . $callback .') was not called'); } @@ -105,35 +114,35 @@ public function testStopListeningRemovesListener() $callback = 'beforePerformEventCallback'; $event = 'beforePerform'; - Resque_Event::listen($event, array($this, $callback)); - Resque_Event::stopListening($event, array($this, $callback)); + Event::listen($event, array($this, $callback)); + Event::stopListening($event, array($this, $callback)); $job = $this->getEventTestJob(); $this->worker->perform($job); $this->worker->work(0); - $this->assertNotContains($callback, $this->callbacksHit, - $event . ' callback (' . $callback .') was called though Resque_Event::stopListening was called' + $this->assertNotContains($callback, $this->callbacksHit, + $event . ' callback (' . $callback .') was called though Resque\Resque_Event::stopListening was called' ); } - + public function beforePerformEventDontPerformCallback($instance) { $this->callbacksHit[] = __FUNCTION__; - throw new Resque_Job_DontPerform; + throw new DontPerform; } - + public function assertValidEventCallback($function, $job) { $this->callbacksHit[] = $function; - if (!$job instanceof Resque_Job) { - $this->fail('Callback job argument is not an instance of Resque_Job'); + if (!$job instanceof Job) { + $this->fail('Callback job argument is not an instance of Resque\Resque_Job'); } $args = $job->getArguments(); $this->assertEquals($args[0], 'somevar'); } - + public function afterEnqueueEventCallback($class, $args) { $this->callbacksHit[] = __FUNCTION__; @@ -142,12 +151,12 @@ public function afterEnqueueEventCallback($class, $args) 'somevar', ), $args); } - + public function beforePerformEventCallback($job) { $this->assertValidEventCallback(__FUNCTION__, $job); } - + public function afterPerformEventCallback($job) { $this->assertValidEventCallback(__FUNCTION__, $job); @@ -157,7 +166,7 @@ public function beforeForkEventCallback($job) { $this->assertValidEventCallback(__FUNCTION__, $job); } - + public function afterForkEventCallback($job) { $this->assertValidEventCallback(__FUNCTION__, $job); diff --git a/test/Resque/Tests/JobStatusTest.php b/test/Resque/Tests/JobStatusTest.php index d751c37f..3fad778c 100644 --- a/test/Resque/Tests/JobStatusTest.php +++ b/test/Resque/Tests/JobStatusTest.php @@ -1,6 +1,12 @@ @@ -9,7 +15,7 @@ class Resque_Tests_JobStatusTest extends Resque_Tests_TestCase { /** - * @var \Resque_Worker + * @var \Resque\Worker */ protected $worker; @@ -18,29 +24,29 @@ public function setUp() parent::setUp(); // Register a worker to test with - $this->worker = new Resque_Worker('jobs'); - $this->worker->setLogger(new Resque_Log()); + $this->worker = new Worker('jobs'); + $this->worker->setLogger(new Log()); } public function testJobStatusCanBeTracked() { $token = Resque::enqueue('jobs', 'Test_Job', null, true); - $status = new Resque_Job_Status($token); + $status = new Status($token); $this->assertTrue($status->isTracking()); } public function testJobStatusIsReturnedViaJobInstance() { $token = Resque::enqueue('jobs', 'Test_Job', null, true); - $job = Resque_Job::reserve('jobs'); - $this->assertEquals(Resque_Job_Status::STATUS_WAITING, $job->getStatus()); + $job = Job::reserve('jobs'); + $this->assertEquals(Status::STATUS_WAITING, $job->getStatus()); } public function testQueuedJobReturnsQueuedStatus() { $token = Resque::enqueue('jobs', 'Test_Job', null, true); - $status = new Resque_Job_Status($token); - $this->assertEquals(Resque_Job_Status::STATUS_WAITING, $status->get()); + $status = new Status($token); + $this->assertEquals(Status::STATUS_WAITING, $status->get()); } public function testRunningJobReturnsRunningStatus() @@ -48,38 +54,38 @@ public function testRunningJobReturnsRunningStatus() $token = Resque::enqueue('jobs', 'Failing_Job', null, true); $job = $this->worker->reserve(); $this->worker->workingOn($job); - $status = new Resque_Job_Status($token); - $this->assertEquals(Resque_Job_Status::STATUS_RUNNING, $status->get()); + $status = new Status($token); + $this->assertEquals(Status::STATUS_RUNNING, $status->get()); } public function testFailedJobReturnsFailedStatus() { $token = Resque::enqueue('jobs', 'Failing_Job', null, true); $this->worker->work(0); - $status = new Resque_Job_Status($token); - $this->assertEquals(Resque_Job_Status::STATUS_FAILED, $status->get()); + $status = new Status($token); + $this->assertEquals(Status::STATUS_FAILED, $status->get()); } public function testCompletedJobReturnsCompletedStatus() { $token = Resque::enqueue('jobs', 'Test_Job', null, true); $this->worker->work(0); - $status = new Resque_Job_Status($token); - $this->assertEquals(Resque_Job_Status::STATUS_COMPLETE, $status->get()); + $status = new Status($token); + $this->assertEquals(Status::STATUS_COMPLETE, $status->get()); } public function testStatusIsNotTrackedWhenToldNotTo() { $token = Resque::enqueue('jobs', 'Test_Job', null, false); - $status = new Resque_Job_Status($token); + $status = new Status($token); $this->assertFalse($status->isTracking()); } public function testStatusTrackingCanBeStopped() { - Resque_Job_Status::create('test'); - $status = new Resque_Job_Status('test'); - $this->assertEquals(Resque_Job_Status::STATUS_WAITING, $status->get()); + Status::create('test'); + $status = new Status('test'); + $this->assertEquals(Status::STATUS_WAITING, $status->get()); $status->stop(); $this->assertFalse($status->get()); } @@ -100,7 +106,7 @@ public function testRecreatedJobWithTrackingStillTracksStatus() $this->assertNotEquals($originalToken, $newToken); // Now check the status of the new job - $newJob = Resque_Job::reserve('jobs'); - $this->assertEquals(Resque_Job_Status::STATUS_WAITING, $newJob->getStatus()); + $newJob = Job::reserve('jobs'); + $this->assertEquals(Status::STATUS_WAITING, $newJob->getStatus()); } } \ No newline at end of file diff --git a/test/Resque/Tests/JobTest.php b/test/Resque/Tests/JobTest.php index d0faa539..f138bfb8 100644 --- a/test/Resque/Tests/JobTest.php +++ b/test/Resque/Tests/JobTest.php @@ -1,7 +1,14 @@ @@ -16,8 +23,8 @@ public function setUp() parent::setUp(); // Register a worker to test with - $this->worker = new Resque_Worker('jobs'); - $this->worker->setLogger(new Resque_Log()); + $this->worker = new Worker('jobs'); + $this->worker->setLogger(new Log()); $this->worker->registerWorker(); } @@ -30,7 +37,7 @@ public function testQeueuedJobCanBeReserved() { Resque::enqueue('jobs', 'Test_Job'); - $job = Resque_Job::reserve('jobs'); + $job = Job::reserve('jobs'); if($job == false) { $this->fail('Job could not be reserved.'); } @@ -62,7 +69,7 @@ public function testQueuedJobReturnsExactSamePassedInArguments() ), ); Resque::enqueue('jobs', 'Test_Job', $args); - $job = Resque_Job::reserve('jobs'); + $job = Job::reserve('jobs'); $this->assertEquals($args, $job->getArguments()); } @@ -70,8 +77,8 @@ public function testQueuedJobReturnsExactSamePassedInArguments() public function testAfterJobIsReservedItIsRemoved() { Resque::enqueue('jobs', 'Test_Job'); - Resque_Job::reserve('jobs'); - $this->assertFalse(Resque_Job::reserve('jobs')); + Job::reserve('jobs'); + $this->assertFalse(Job::reserve('jobs')); } public function testRecreatedJobMatchesExistingJob() @@ -89,12 +96,12 @@ public function testRecreatedJobMatchesExistingJob() ); Resque::enqueue('jobs', 'Test_Job', $args); - $job = Resque_Job::reserve('jobs'); + $job = Job::reserve('jobs'); // Now recreate it $job->recreate(); - $newJob = Resque_Job::reserve('jobs'); + $newJob = Job::reserve('jobs'); $this->assertEquals($job->payload['class'], $newJob->payload['class']); $this->assertEquals($job->getArguments(), $newJob->getArguments()); } @@ -106,17 +113,17 @@ public function testFailedJobExceptionsAreCaught() 'class' => 'Failing_Job', 'args' => null ); - $job = new Resque_Job('jobs', $payload); + $job = new Job('jobs', $payload); $job->worker = $this->worker; $this->worker->perform($job); - $this->assertEquals(1, Resque_Stat::get('failed')); - $this->assertEquals(1, Resque_Stat::get('failed:'.$this->worker)); + $this->assertEquals(1, Stat::get('failed')); + $this->assertEquals(1, Stat::get('failed:'.$this->worker)); } /** - * @expectedException Resque_Exception + * @expectedException Exception */ public function testJobWithoutPerformMethodThrowsException() { @@ -127,7 +134,7 @@ public function testJobWithoutPerformMethodThrowsException() } /** - * @expectedException Resque_Exception + * @expectedException Exception */ public function testInvalidJobThrowsException() { @@ -136,7 +143,7 @@ public function testInvalidJobThrowsException() $job->worker = $this->worker; $job->perform(); } - + public function testJobWithSetUpCallbackFiresSetUp() { $payload = array( @@ -146,12 +153,12 @@ public function testJobWithSetUpCallbackFiresSetUp() 'somevar2', ), ); - $job = new Resque_Job('jobs', $payload); + $job = new Job('jobs', $payload); $job->perform(); - + $this->assertTrue(Test_Job_With_SetUp::$called); } - + public function testJobWithTearDownCallbackFiresTearDown() { $payload = array( @@ -161,24 +168,24 @@ public function testJobWithTearDownCallbackFiresTearDown() 'somevar2', ), ); - $job = new Resque_Job('jobs', $payload); + $job = new Job('jobs', $payload); $job->perform(); - + $this->assertTrue(Test_Job_With_TearDown::$called); } - + public function testJobWithNamespace() { - Resque_Redis::prefix('php'); + Redis::prefix('php'); $queue = 'jobs'; $payload = array('another_value'); Resque::enqueue($queue, 'Test_Job_With_TearDown', $payload); - + $this->assertEquals(Resque::queues(), array('jobs')); $this->assertEquals(Resque::size($queue), 1); - - Resque_Redis::prefix('resque'); - $this->assertEquals(Resque::size($queue), 0); + + Redis::prefix('resque'); + $this->assertEquals(Resque::size($queue), 0); } public function testDequeueAll() @@ -296,7 +303,7 @@ public function testDequeueItemWithArg() $this->assertEquals(Resque::size($queue), 2); $test = array('Test_Job_Dequeue9' => $arg); $this->assertEquals(Resque::dequeue($queue, $test), 1); - #$this->assertEquals(Resque::size($queue), 1); + #$this->assertEquals(Resque\Resque::size($queue), 1); } public function testDequeueItemWithUnorderedArg() diff --git a/test/Resque/Tests/LogTest.php b/test/Resque/Tests/LogTest.php index db97b160..ea12d497 100644 --- a/test/Resque/Tests/LogTest.php +++ b/test/Resque/Tests/LogTest.php @@ -1,6 +1,8 @@ @@ -10,7 +12,7 @@ class Resque_Tests_LogTest extends Resque_Tests_TestCase { public function testLogInterpolate() { - $logger = new Resque_Log(); + $logger = new Log(); $actual = $logger->interpolate('string {replace}', array('replace' => 'value')); $expected = 'string value'; @@ -19,7 +21,7 @@ public function testLogInterpolate() public function testLogInterpolateMutiple() { - $logger = new Resque_Log(); + $logger = new Log(); $actual = $logger->interpolate( 'string {replace1} {replace2}', array('replace1' => 'value1', 'replace2' => 'value2') diff --git a/test/Resque/Tests/StatTest.php b/test/Resque/Tests/StatTest.php index aa418887..2bc1a57a 100644 --- a/test/Resque/Tests/StatTest.php +++ b/test/Resque/Tests/StatTest.php @@ -1,6 +1,8 @@ @@ -10,40 +12,40 @@ class Resque_Tests_StatTest extends Resque_Tests_TestCase { public function testStatCanBeIncremented() { - Resque_Stat::incr('test_incr'); - Resque_Stat::incr('test_incr'); + Stat::incr('test_incr'); + Stat::incr('test_incr'); $this->assertEquals(2, $this->redis->get('resque:stat:test_incr')); } public function testStatCanBeIncrementedByX() { - Resque_Stat::incr('test_incrX', 10); - Resque_Stat::incr('test_incrX', 11); + Stat::incr('test_incrX', 10); + Stat::incr('test_incrX', 11); $this->assertEquals(21, $this->redis->get('resque:stat:test_incrX')); } public function testStatCanBeDecremented() { - Resque_Stat::incr('test_decr', 22); - Resque_Stat::decr('test_decr'); + Stat::incr('test_decr', 22); + Stat::decr('test_decr'); $this->assertEquals(21, $this->redis->get('resque:stat:test_decr')); } public function testStatCanBeDecrementedByX() { - Resque_Stat::incr('test_decrX', 22); - Resque_Stat::decr('test_decrX', 11); + Stat::incr('test_decrX', 22); + Stat::decr('test_decrX', 11); $this->assertEquals(11, $this->redis->get('resque:stat:test_decrX')); } public function testGetStatByName() { - Resque_Stat::incr('test_get', 100); - $this->assertEquals(100, Resque_Stat::get('test_get')); + Stat::incr('test_get', 100); + $this->assertEquals(100, Stat::get('test_get')); } public function testGetUnknownStatReturns0() { - $this->assertEquals(0, Resque_Stat::get('test_get_unknown')); + $this->assertEquals(0, Stat::get('test_get_unknown')); } } \ No newline at end of file diff --git a/test/Resque/Tests/WorkerTest.php b/test/Resque/Tests/WorkerTest.php index 93c0621a..62bd14e9 100644 --- a/test/Resque/Tests/WorkerTest.php +++ b/test/Resque/Tests/WorkerTest.php @@ -1,6 +1,12 @@ @@ -10,8 +16,8 @@ class Resque_Tests_WorkerTest extends Resque_Tests_TestCase { public function testWorkerRegistersInList() { - $worker = new Resque_Worker('*'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('*'); + $worker->setLogger(new Log()); $worker->registerWorker(); // Make sure the worker is in the list @@ -23,73 +29,73 @@ public function testGetAllWorkers() $num = 3; // Register a few workers for($i = 0; $i < $num; ++$i) { - $worker = new Resque_Worker('queue_' . $i); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('queue_' . $i); + $worker->setLogger(new Log()); $worker->registerWorker(); } // Now try to get them - $this->assertEquals($num, count(Resque_Worker::all())); + $this->assertEquals($num, count(Worker::all())); } public function testGetWorkerById() { - $worker = new Resque_Worker('*'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('*'); + $worker->setLogger(new Log()); $worker->registerWorker(); - $newWorker = Resque_Worker::find((string)$worker); + $newWorker = Worker::find((string)$worker); $this->assertEquals((string)$worker, (string)$newWorker); } public function testInvalidWorkerDoesNotExist() { - $this->assertFalse(Resque_Worker::exists('blah')); + $this->assertFalse(Worker::exists('blah')); } public function testWorkerCanUnregister() { - $worker = new Resque_Worker('*'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('*'); + $worker->setLogger(new Log()); $worker->registerWorker(); $worker->unregisterWorker(); - $this->assertFalse(Resque_Worker::exists((string)$worker)); - $this->assertEquals(array(), Resque_Worker::all()); + $this->assertFalse(Worker::exists((string)$worker)); + $this->assertEquals(array(), Worker::all()); $this->assertEquals(array(), $this->redis->smembers('resque:workers')); } public function testPausedWorkerDoesNotPickUpJobs() { - $worker = new Resque_Worker('*'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('*'); + $worker->setLogger(new Log()); $worker->pauseProcessing(); Resque::enqueue('jobs', 'Test_Job'); $worker->work(0); $worker->work(0); - $this->assertEquals(0, Resque_Stat::get('processed')); + $this->assertEquals(0, Stat::get('processed')); } public function testResumedWorkerPicksUpJobs() { - $worker = new Resque_Worker('*'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('*'); + $worker->setLogger(new Log()); $worker->pauseProcessing(); Resque::enqueue('jobs', 'Test_Job'); $worker->work(0); - $this->assertEquals(0, Resque_Stat::get('processed')); + $this->assertEquals(0, Stat::get('processed')); $worker->unPauseProcessing(); $worker->work(0); - $this->assertEquals(1, Resque_Stat::get('processed')); + $this->assertEquals(1, Stat::get('processed')); } public function testWorkerCanWorkOverMultipleQueues() { - $worker = new Resque_Worker(array( + $worker = new Worker(array( 'queue1', 'queue2' )); - $worker->setLogger(new Resque_Log()); + $worker->setLogger(new Log()); $worker->registerWorker(); Resque::enqueue('queue1', 'Test_Job_1'); Resque::enqueue('queue2', 'Test_Job_2'); @@ -103,12 +109,12 @@ public function testWorkerCanWorkOverMultipleQueues() public function testWorkerWorksQueuesInSpecifiedOrder() { - $worker = new Resque_Worker(array( + $worker = new Worker(array( 'high', 'medium', 'low' )); - $worker->setLogger(new Resque_Log()); + $worker->setLogger(new Log()); $worker->registerWorker(); // Queue the jobs in a different order @@ -129,8 +135,8 @@ public function testWorkerWorksQueuesInSpecifiedOrder() public function testWildcardQueueWorkerWorksAllQueues() { - $worker = new Resque_Worker('*'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('*'); + $worker->setLogger(new Log()); $worker->registerWorker(); Resque::enqueue('queue1', 'Test_Job_1'); @@ -145,8 +151,8 @@ public function testWildcardQueueWorkerWorksAllQueues() public function testWorkerDoesNotWorkOnUnknownQueues() { - $worker = new Resque_Worker('queue1'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('queue1'); + $worker->setLogger(new Log()); $worker->registerWorker(); Resque::enqueue('queue2', 'Test_Job'); @@ -156,8 +162,8 @@ public function testWorkerDoesNotWorkOnUnknownQueues() public function testWorkerClearsItsStatusWhenNotWorking() { Resque::enqueue('jobs', 'Test_Job'); - $worker = new Resque_Worker('jobs'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('jobs'); + $worker->setLogger(new Log()); $job = $worker->reserve(); $worker->workingOn($job); $worker->doneWorking(); @@ -166,14 +172,14 @@ public function testWorkerClearsItsStatusWhenNotWorking() public function testWorkerRecordsWhatItIsWorkingOn() { - $worker = new Resque_Worker('jobs'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('jobs'); + $worker->setLogger(new Log()); $worker->registerWorker(); $payload = array( 'class' => 'Test_Job' ); - $job = new Resque_Job('jobs', $payload); + $job = new Job('jobs', $payload); $worker->workingOn($job); $job = $worker->job(); @@ -189,8 +195,8 @@ public function testWorkerErasesItsStatsWhenShutdown() Resque::enqueue('jobs', 'Test_Job'); Resque::enqueue('jobs', 'Invalid_Job'); - $worker = new Resque_Worker('jobs'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('jobs'); + $worker->setLogger(new Log()); $worker->work(0); $worker->work(0); @@ -201,76 +207,76 @@ public function testWorkerErasesItsStatsWhenShutdown() public function testWorkerCleansUpDeadWorkersOnStartup() { // Register a good worker - $goodWorker = new Resque_Worker('jobs'); - $goodWorker->setLogger(new Resque_Log()); + $goodWorker = new Worker('jobs'); + $goodWorker->setLogger(new Log()); $goodWorker->registerWorker(); $workerId = explode(':', $goodWorker); // Register some bad workers - $worker = new Resque_Worker('jobs'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('jobs'); + $worker->setLogger(new Log()); $worker->setId($workerId[0].':1:jobs'); $worker->registerWorker(); - $worker = new Resque_Worker(array('high', 'low')); - $worker->setLogger(new Resque_Log()); + $worker = new Worker(array('high', 'low')); + $worker->setLogger(new Log()); $worker->setId($workerId[0].':2:high,low'); $worker->registerWorker(); - $this->assertEquals(3, count(Resque_Worker::all())); + $this->assertEquals(3, count(Worker::all())); $goodWorker->pruneDeadWorkers(); // There should only be $goodWorker left now - $this->assertEquals(1, count(Resque_Worker::all())); + $this->assertEquals(1, count(Worker::all())); } public function testDeadWorkerCleanUpDoesNotCleanUnknownWorkers() { // Register a bad worker on this machine - $worker = new Resque_Worker('jobs'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('jobs'); + $worker->setLogger(new Log()); $workerId = explode(':', $worker); $worker->setId($workerId[0].':1:jobs'); $worker->registerWorker(); // Register some other false workers - $worker = new Resque_Worker('jobs'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('jobs'); + $worker->setLogger(new Log()); $worker->setId('my.other.host:1:jobs'); $worker->registerWorker(); - $this->assertEquals(2, count(Resque_Worker::all())); + $this->assertEquals(2, count(Worker::all())); $worker->pruneDeadWorkers(); // my.other.host should be left - $workers = Resque_Worker::all(); + $workers = Worker::all(); $this->assertEquals(1, count($workers)); $this->assertEquals((string)$worker, (string)$workers[0]); } public function testWorkerFailsUncompletedJobsOnExit() { - $worker = new Resque_Worker('jobs'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('jobs'); + $worker->setLogger(new Log()); $worker->registerWorker(); $payload = array( 'class' => 'Test_Job' ); - $job = new Resque_Job('jobs', $payload); + $job = new Job('jobs', $payload); $worker->workingOn($job); $worker->unregisterWorker(); - $this->assertEquals(1, Resque_Stat::get('failed')); + $this->assertEquals(1, Stat::get('failed')); } public function testBlockingListPop() { - $worker = new Resque_Worker('jobs'); - $worker->setLogger(new Resque_Log()); + $worker = new Worker('jobs'); + $worker->setLogger(new Log()); $worker->registerWorker(); Resque::enqueue('jobs', 'Test_Job_1'); diff --git a/test/bootstrap.php b/test/bootstrap.php index a4b68377..cdeebc1b 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -7,13 +7,15 @@ * @license http://www.opensource.org/licenses/mit-license.php */ +use Resque\Resque; + $loader = require __DIR__ . '/../vendor/autoload.php'; $loader->add('Resque_Tests', __DIR__); define('TEST_MISC', realpath(__DIR__ . '/misc/')); define('REDIS_CONF', TEST_MISC . '/redis.conf'); -// Attempt to start our own redis instance for tesitng. +// Attempt to start our own redis instance for testing. exec('which redis-server', $output, $returnVar); if($returnVar != 0) { echo "Cannot find redis-server in path. Please make sure redis is installed.\n";