| 1 |
= sfJobQueuePlugin = |
|---|
| 2 |
|
|---|
| 3 |
[[PageOutline]] |
|---|
| 4 |
|
|---|
| 5 |
== Introduction == |
|---|
| 6 |
=== What the **** ? === |
|---|
| 7 |
This plugins enables job queues into Symfony. It includes all the common job |
|---|
| 8 |
queues tasks (start, stop, scheduling through job election strategies, etc.), |
|---|
| 9 |
command line tasks, and a graphical interface for managing queues and jobs. |
|---|
| 10 |
Using a job queue can be useful when asynchronised server-side operations have |
|---|
| 11 |
to be performed (periodically grabbing a RSS feed, automatically sending |
|---|
| 12 |
emails, etc.) or in environments without a cron access. |
|---|
| 13 |
|
|---|
| 14 |
The advantages compared to cron are: |
|---|
| 15 |
* a more acurate job-execution scheduling (cron's best precision is at the minute level) |
|---|
| 16 |
* error logging |
|---|
| 17 |
* jobs creation from a graphical interface |
|---|
| 18 |
* API to create jobs programmatically |
|---|
| 19 |
|
|---|
| 20 |
=== Requirements === |
|---|
| 21 |
* a shell access |
|---|
| 22 |
* the right to run PHP from the command line |
|---|
| 23 |
* this plugin has not been tested with windows |
|---|
| 24 |
|
|---|
| 25 |
=== Glossary === |
|---|
| 26 |
A "job" is a task that has to be done. In this plugin, it is modeled as a call |
|---|
| 27 |
to a JobHandler, which understands the parameters associated to the job in |
|---|
| 28 |
order to perform a dedicated action. |
|---|
| 29 |
|
|---|
| 30 |
A "job queue" is a group of jobs to be run, eventually several times, |
|---|
| 31 |
associated to a job election strategy. For the moment, one job queue can only |
|---|
| 32 |
run one job at a time. |
|---|
| 33 |
|
|---|
| 34 |
The "job queue manager" is the process that is responsible for the launch of |
|---|
| 35 |
the job queues. It creates a PHP process for every queue the user has requested |
|---|
| 36 |
the start. |
|---|
| 37 |
|
|---|
| 38 |
== Features == |
|---|
| 39 |
* multi-queues support |
|---|
| 40 |
* one given queue can contain heterogeneous job types |
|---|
| 41 |
* scheduling (job election) strategy abstraction |
|---|
| 42 |
* support for reccuring jobs |
|---|
| 43 |
* CLI tasks |
|---|
| 44 |
* Queue and jobs management graphical module: |
|---|
| 45 |
* job-queues start/stop |
|---|
| 46 |
* job-queues statistics |
|---|
| 47 |
* job creation |
|---|
| 48 |
|
|---|
| 49 |
=== Job-queues admin panel === |
|---|
| 50 |
[[Image(sfJobQueuePlugin_admin_panel.png)]] |
|---|
| 51 |
|
|---|
| 52 |
|
|---|
| 53 |
== Get it installed == |
|---|
| 54 |
* go to your project's root |
|---|
| 55 |
|
|---|
| 56 |
* Install the plugin: |
|---|
| 57 |
{{{ |
|---|
| 58 |
./symfony plugin-install http://plugins.symfony-project.com/sfJobQueuePlugin |
|---|
| 59 |
}}} |
|---|
| 60 |
|
|---|
| 61 |
* rebuild the model: |
|---|
| 62 |
{{{ |
|---|
| 63 |
./symfony propel-build-all |
|---|
| 64 |
}}} |
|---|
| 65 |
|
|---|
| 66 |
* enable the "sfJob" and the "sfJobQueue" modules in your app's settings.yml: |
|---|
| 67 |
{{{ |
|---|
| 68 |
enabled_modules: [default, sfJob, sfJobQueue] |
|---|
| 69 |
}}} |
|---|
| 70 |
|
|---|
| 71 |
* clear cache: |
|---|
| 72 |
{{{ |
|---|
| 73 |
./symfony cc |
|---|
| 74 |
}}} |
|---|
| 75 |
|
|---|
| 76 |
|
|---|
| 77 |
== Usage == |
|---|
| 78 |
|
|---|
| 79 |
=== Starting the job queues manager === |
|---|
| 80 |
The "queue manager" is the process that is responsible for the management of |
|---|
| 81 |
the job queues. When requested, it is able a given job queue. No job will be run |
|---|
| 82 |
until you start the queues manager. |
|---|
| 83 |
|
|---|
| 84 |
As this process must be able to create child processes, it has to be runned from |
|---|
| 85 |
the command line. This notably means that you must have a shell access to your |
|---|
| 86 |
server in order to be able to use this plugin. |
|---|
| 87 |
|
|---|
| 88 |
Starting the queue manager can be done with the following Symfony task: |
|---|
| 89 |
{{{ |
|---|
| 90 |
$ ./symfony sfqueue-start-queuemanager YOUR_APP_NAME |
|---|
| 91 |
}}} |
|---|
| 92 |
|
|---|
| 93 |
You usually will want that the executions continues when you disconnect from |
|---|
| 94 |
your server. Therefore, you should rather run the following command: |
|---|
| 95 |
{{{ |
|---|
| 96 |
$ nohup ./symfony sfqueue-start-queuemanager YOUR_APP_NAME & |
|---|
| 97 |
}}} |
|---|
| 98 |
|
|---|
| 99 |
Doing so will forbid you to stop it without using the "kill" command. If this |
|---|
| 100 |
sounds like a problem to you, you will probably be interested in the |
|---|
| 101 |
[http://www.gnu.org/software/screen screen] utility. |
|---|
| 102 |
|
|---|
| 103 |
=== Creating a queue === |
|---|
| 104 |
In order to create a job queue, you must first activate the administration |
|---|
| 105 |
module. You can then create a job queue by defining: |
|---|
| 106 |
* its name |
|---|
| 107 |
* its job election strategy (either FIFO or priority-based). Other job-election |
|---|
| 108 |
strategies may be developed in the future. |
|---|
| 109 |
|
|---|
| 110 |
You usually won't need it, but it is also possible to create job queues |
|---|
| 111 |
dynamically, directly from your application's code: |
|---|
| 112 |
{{{ |
|---|
| 113 |
#!php |
|---|
| 114 |
<?php |
|---|
| 115 |
$queue = new sfJobQueue(); |
|---|
| 116 |
$queue->setName('RSS grabbing queue'); |
|---|
| 117 |
$queue->setSchedulerName('fifo'); |
|---|
| 118 |
$queue->save(); |
|---|
| 119 |
}}} |
|---|
| 120 |
|
|---|
| 121 |
By default, the job queue is stopped when created. In order it to get active, |
|---|
| 122 |
you need to start it. |
|---|
| 123 |
|
|---|
| 124 |
=== Running a job queue === |
|---|
| 125 |
Starting a job queue can eitherbe done from the graphical interface, or directly |
|---|
| 126 |
in the code: |
|---|
| 127 |
{{{ |
|---|
| 128 |
#!php |
|---|
| 129 |
<?php |
|---|
| 130 |
$queue->setRequestedStatus(sfJobQueue::RUNNING); |
|---|
| 131 |
$queue->save(); |
|---|
| 132 |
}}} |
|---|
| 133 |
|
|---|
| 134 |
A job queue can also be started with Symfony's command-line utility: |
|---|
| 135 |
{{{ |
|---|
| 136 |
./symfony sfqueue-start-queue YOUR_APP_NAME QUEUE_NAME |
|---|
| 137 |
}}} |
|---|
| 138 |
|
|---|
| 139 |
So, for instance : |
|---|
| 140 |
|
|---|
| 141 |
{{{ |
|---|
| 142 |
./symfony sfqueue-start-queue frontend 'RSS grabbing queue' |
|---|
| 143 |
}}} |
|---|
| 144 |
|
|---|
| 145 |
=== Adding a job to the JobQueue === |
|---|
| 146 |
Adding a job to one job queue is rather simple. You only have to give the type |
|---|
| 147 |
of the job which has to be created, and set its execution parameters. |
|---|
| 148 |
{{{ |
|---|
| 149 |
#!php |
|---|
| 150 |
<?php |
|---|
| 151 |
$queue->addJob('mail', array('to' => 'xavier@lacot.org', 'topic' => 'Testing the MailingJobQueue :)')); |
|---|
| 152 |
}}} |
|---|
| 153 |
|
|---|
| 154 |
This means that you will have to create, somewhere in your project (why not in |
|---|
| 155 |
another plugin ;)) a JobHandler that supports the "mail" job type. For more |
|---|
| 156 |
details, see the paragraph "[#Creatinganewjobtype Creating a new job type]". |
|---|
| 157 |
|
|---|
| 158 |
== Job parameters == |
|---|
| 159 |
A job queue can be tweaked using several parameters: |
|---|
| 160 |
* scheduler_name: the scheduler name defines the policy of job election. For instance, with a "fifo" scheduler, the oldest eligible job will be the one who will be processed next. With a "priority" scheduler, the eligible job with the highest priority will be the first out. |
|---|
| 161 |
* polling_delay: time between the end of the execution of one job, and the next job election |
|---|
| 162 |
|
|---|
| 163 |
[[Image(sfJobQueuePlugin_job_election.png)]] |
|---|
| 164 |
|
|---|
| 165 |
At the job level, it is also possible to set some general execution parameters: |
|---|
| 166 |
* max_tries: |
|---|
| 167 |
* retry_delay: retry delay, in seconds (minimal delay between two tries of the same job) |
|---|
| 168 |
* priority: priority : from 0 to 9 (lower to higher) |
|---|
| 169 |
* params: an array of parameters for the job execution |
|---|
| 170 |
* scheduled_at: date of scheduling after which the job can be runned |
|---|
| 171 |
|
|---|
| 172 |
== Creating a new job type == |
|---|
| 173 |
The sfJobQueuePlugin has been designed so that creating a new job type should |
|---|
| 174 |
be the less painful possible for the developer. Actually, developping a new job |
|---|
| 175 |
type only requires one single {{{JobHandler}}} class to be written, that |
|---|
| 176 |
implements the interface |
|---|
| 177 |
[browser:plugins/sfJobQueuePlugin/lib/jobHandlers/sfJobHandlerInterface.class.php sfJobHandlerInterface]. |
|---|
| 178 |
Nothing else. |
|---|
| 179 |
|
|---|
| 180 |
The name of this class is important, as the plugins gets the name of the job |
|---|
| 181 |
type from it. For instance, if you create a "sfClearCacheJobHandler", the plugin |
|---|
| 182 |
graphical interface will automagically propose the job type "{{{ClearCache}}}" |
|---|
| 183 |
during the creation of new jobs. |
|---|
| 184 |
|
|---|
| 185 |
Here is for instance a minimal {{{JobHandler}}}: |
|---|
| 186 |
|
|---|
| 187 |
{{{ |
|---|
| 188 |
#!php |
|---|
| 189 |
<?php |
|---|
| 190 |
class sfMailJobHandler extends sfJobHandler implements sfJobHandlerInterface |
|---|
| 191 |
{ |
|---|
| 192 |
public function getParamFields() |
|---|
| 193 |
{ |
|---|
| 194 |
return array('from', 'to', 'subject', 'message'); |
|---|
| 195 |
} |
|---|
| 196 |
|
|---|
| 197 |
public function run($params) |
|---|
| 198 |
{ |
|---|
| 199 |
if (mail($params['to'], |
|---|
| 200 |
$params['subject'], |
|---|
| 201 |
$params['message'], |
|---|
| 202 |
'From: '.$params['from'])) |
|---|
| 203 |
{ |
|---|
| 204 |
return sfJob::SUCCESS; |
|---|
| 205 |
} |
|---|
| 206 |
else |
|---|
| 207 |
{ |
|---|
| 208 |
throw new Exception('There was an error while sending the mail.'); |
|---|
| 209 |
return sfJob::ERROR; |
|---|
| 210 |
} |
|---|
| 211 |
} |
|---|
| 212 |
} |
|---|
| 213 |
}}} |
|---|
| 214 |
|
|---|
| 215 |
Several remarks about it: |
|---|
| 216 |
=== getParamFields() === |
|---|
| 217 |
This must return the name of the parameters expected when running the job. It |
|---|
| 218 |
is used by the graphical interface, in order to ease up the job creation. In |
|---|
| 219 |
the graphical interface, the fields will be displayed in the order of the |
|---|
| 220 |
elements in this array: |
|---|
| 221 |
|
|---|
| 222 |
[[Image(sfJobQueuePlugin_job_creation_detail.png)]] |
|---|
| 223 |
|
|---|
| 224 |
=== run($params) === |
|---|
| 225 |
The method '''{{{run()}}}''' uses an array of parameters for completing the |
|---|
| 226 |
job. It must do a proper use of the return status, either {{{sfJob::SUCCESS}}} |
|---|
| 227 |
in case the job is successful, or {{{sfJob::ERROR}}} if there is an error. |
|---|
| 228 |
Remember that, if a PHP error is raised while running the job, its execution |
|---|
| 229 |
will be stopped and the job will be marked as failed. You'll then be able to |
|---|
| 230 |
track the error on the graphical interface: |
|---|
| 231 |
|
|---|
| 232 |
[[Image(sfJobQueuePlugin_error_display.png)]] |
|---|
| 233 |
|
|---|
| 234 |
The {{{run()}}} method may launch exceptions. In this case, the job will be |
|---|
| 235 |
marked as failed, except if it is a recuring job. |
|---|
| 236 |
|
|---|
| 237 |
=== static postSave($job, $params) === |
|---|
| 238 |
This "hook" is called after one job object is created. Depending on the job |
|---|
| 239 |
type, you may want to do several operations along with the job creation. And |
|---|
| 240 |
these operations may also modify the parameters of the job (for instance, for |
|---|
| 241 |
adding the id of a new object created during the hook, etc.) |
|---|
| 242 |
|
|---|
| 243 |
If the job creation must perform other operations that the job execution must |
|---|
| 244 |
be aware of, then the optionnal static method {{{postSave($job, $params)}}} is |
|---|
| 245 |
the right place for this. |
|---|
| 246 |
|
|---|
| 247 |
For instance: |
|---|
| 248 |
{{{ |
|---|
| 249 |
public static function postSave($job, $params) |
|---|
| 250 |
{ |
|---|
| 251 |
$feed = new AggregatorFeed(); |
|---|
| 252 |
$feed->setUri($params['uri']); |
|---|
| 253 |
$feed->save(); |
|---|
| 254 |
$params['feed_id'] = $feed->getId(); |
|---|
| 255 |
$job->setParams(serialize($params)); |
|---|
| 256 |
$job->save(); |
|---|
| 257 |
} |
|---|
| 258 |
}}} |
|---|
| 259 |
|
|---|
| 260 |
== API == |
|---|
| 261 |
sfJobQueue: |
|---|
| 262 |
* '''{{{addJob($type = '', $options = null)}}}''' - Creates a new job in the queue |
|---|
| 263 |
* '''{{{getNbActiveJobs()}}}''' - Returns the number of active jobs, ie. jobs that are neither failed, nor successful, nor cancelled |
|---|
| 264 |
* '''{{{getNbActiveReadyJobs()}}}''' - Returns the number of active jobs ready to be runned |
|---|
| 265 |
* '''{{{getNbActiveRecuringJobs()}}}''' - Returns the number of active recuring jobs |
|---|
| 266 |
* '''{{{getNbActiveScheduledJobs()}}}''' - Returns the number of active scheduled jobs (ie. scheduled in the future) |
|---|
| 267 |
* '''{{{getNbActiveWaitingJobs()}}}''' - Returns the number of active waiting jobs (ie. active, but not ready to be runned) |
|---|
| 268 |
* '''{{{getNbCompletedCancelledJobs()}}}''' - Returns the number of jobs that have been cancelled |
|---|
| 269 |
* '''{{{getNbCompletedFailureJobs()}}}''' - Returns the number of jobs completed on failure |
|---|
| 270 |
* '''{{{getNbCompletedSuccessfulJobs()}}}''' - Returns the number of successfully runned jobs |
|---|
| 271 |
* '''{{{getNbCompletedJobs()}}}''' - Returns the number of completed jobs |
|---|
| 272 |
* '''{{{getStatusText()}}}''' - Returns the status of the queue, as a text |
|---|
| 273 |
* '''{{{isRunning()}}}''' - Indicates whether the queue is running or not |
|---|
| 274 |
* '''{{{run()}}}''' - Runs the queue |
|---|
| 275 |
|
|---|
| 276 |
sfJob: |
|---|
| 277 |
* '''{{{getStatusText()}}}''' - Returns the status of the job, as a text |
|---|
| 278 |
* '''{{{run()}}}''' - Runs the job |
|---|
| 279 |
|
|---|
| 280 |
== Unit testing == |
|---|
| 281 |
To be done. |
|---|
| 282 |
|
|---|
| 283 |
== Roadmap == |
|---|
| 284 |
* job messages detail |
|---|
| 285 |
* have several jobs running at one time |
|---|
| 286 |
* unit-testing |
|---|
| 287 |
* job reporting hooks (measure jobs progress) |
|---|
| 288 |
* handle dependancy between jobs |
|---|
| 289 |
|
|---|
| 290 |
Long term runners might want to see: |
|---|
| 291 |
* integration with http://yowl.googlecode.com/svn/trunk/test/test-yowl.html |
|---|
| 292 |
* exposition to Web services |
|---|
| 293 |
|
|---|
| 294 |
== Bibliograhy == |
|---|
| 295 |
* http://en.wikipedia.org/wiki/Queue_%28data_structure%29 |
|---|
| 296 |
* http://en.wikipedia.org/wiki/FIFO |
|---|
| 297 |
* http://en.wikipedia.org/wiki/Priority_queue |
|---|
| 298 |
|
|---|
| 299 |
== License and credits == |
|---|
| 300 |
This plugin is licensed under the MIT license and maintained by [http://lacot.org/ Xavier Lacot] |
|---|
| 301 |
<xavier@lacot.org>. External contributions and comments are welcome ! |
|---|
| 302 |
|
|---|
| 303 |
== Changelog == |
|---|
| 304 |
|
|---|
| 305 |
=== version 0.2 - 2007-10-23 === |
|---|
| 306 |
* upgraded the graphical interface : |
|---|
| 307 |
* possibility to create jobs |
|---|
| 308 |
* support for recuring jobs |
|---|
| 309 |
* support for jobs scheduling |
|---|
| 310 |
* improved documentation |
|---|
| 311 |
* introduced job queue manager concept |
|---|
| 312 |
|
|---|
| 313 |
=== version 0.1 - 2007-09-17 === |
|---|
| 314 |
Initial public release. |
|---|