did_workling 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/README.markdown +382 -0
  2. data/lib/rude_q/client.rb +11 -0
  3. data/lib/workling.rb +150 -0
  4. data/lib/workling/base.rb +59 -0
  5. data/lib/workling/clients/amqp_client.rb +40 -0
  6. data/lib/workling/clients/base.rb +54 -0
  7. data/lib/workling/clients/memcache_queue_client.rb +82 -0
  8. data/lib/workling/discovery.rb +14 -0
  9. data/lib/workling/remote.rb +42 -0
  10. data/lib/workling/remote/invokers/base.rb +124 -0
  11. data/lib/workling/remote/invokers/basic_poller.rb +41 -0
  12. data/lib/workling/remote/invokers/eventmachine_subscriber.rb +37 -0
  13. data/lib/workling/remote/invokers/threaded_poller.rb +149 -0
  14. data/lib/workling/remote/runners/backgroundjob_runner.rb +35 -0
  15. data/lib/workling/remote/runners/base.rb +42 -0
  16. data/lib/workling/remote/runners/client_runner.rb +45 -0
  17. data/lib/workling/remote/runners/not_remote_runner.rb +23 -0
  18. data/lib/workling/remote/runners/spawn_runner.rb +38 -0
  19. data/lib/workling/remote/runners/starling_runner.rb +13 -0
  20. data/lib/workling/return/store/base.rb +37 -0
  21. data/lib/workling/return/store/memory_return_store.rb +26 -0
  22. data/lib/workling/return/store/starling_return_store.rb +31 -0
  23. data/lib/workling/routing/base.rb +13 -0
  24. data/lib/workling/routing/class_and_method_routing.rb +55 -0
  25. data/test/class_and_method_routing_test.rb +18 -0
  26. data/test/clients/memory_queue_client.rb +36 -0
  27. data/test/discovery_test.rb +13 -0
  28. data/test/invoker_basic_poller_test.rb +29 -0
  29. data/test/invoker_eventmachine_subscription_test.rb +26 -0
  30. data/test/invoker_threaded_poller_test.rb +34 -0
  31. data/test/memcachequeue_client_test.rb +36 -0
  32. data/test/memory_return_store_test.rb +23 -0
  33. data/test/mocks/client.rb +9 -0
  34. data/test/mocks/logger.rb +5 -0
  35. data/test/mocks/spawn.rb +5 -0
  36. data/test/not_remote_runner_test.rb +11 -0
  37. data/test/remote_runner_test.rb +50 -0
  38. data/test/return_store_test.rb +18 -0
  39. data/test/runners/thread_runner.rb +22 -0
  40. data/test/spawn_runner_test.rb +10 -0
  41. data/test/starling_return_store_test.rb +29 -0
  42. data/test/starling_runner_test.rb +8 -0
  43. data/test/test_helper.rb +48 -0
  44. data/test/workers/analytics/invites.rb +10 -0
  45. data/test/workers/util.rb +15 -0
  46. metadata +132 -0
@@ -0,0 +1,382 @@
1
+ # Workling
2
+
3
+ Workling gives your Rails App a simple API that you can use to make code run in the background, outside of the your request.
4
+
5
+ You can configure how the background code will be run. Currently, workling supports Starling, BackgroundJob and Spawn Runners. Workling is a bit like Actve* for background work: you can write your code once, then swap in any of the supported background Runners later. This keeps things flexible.
6
+
7
+ ## Installing Workling
8
+
9
+ The easiest way of getting started with workling is like this:
10
+
11
+ script/plugin install git://github.com/purzelrakete/workling.git
12
+ script/plugin install git://github.com/tra/spawn.git
13
+
14
+ If you're on an older Rails version, there's also a subversion mirror wor workling (I'll do my best to keep it synched) at:
15
+
16
+ script/plugin install http://svn.playtype.net/plugins/workling/
17
+
18
+ ## Writing and calling Workers
19
+
20
+ This is pretty easy. Just put `cow_worker.rb` into into `app/workers`, and subclass `Workling::Base`:
21
+
22
+ # handle asynchronous mooing.
23
+ class CowWorker < Workling::Base
24
+ def moo(options)
25
+ cow = Cow.find(options[:id])
26
+ logger.info("about to moo.")
27
+ cow.moo
28
+ end
29
+ end
30
+
31
+ Make sure you have exactly one hash parameter in your methods, workling passes the job :uid into here. Btw, in case you want to follow along with the Mooing, grab 'cows-not-kittens' off github, it's an example workling project. Look at the branches, there's one for each Runner.
32
+
33
+ Next, you'll want to call your workling in a controller. Your controller might looks like this:
34
+
35
+ class CowsController < ApplicationController
36
+
37
+ # milking has the side effect of causing
38
+ # the cow to moo. we don't want to
39
+ # wait for this while milking, though,
40
+ # it would be a terrible waste ouf our time.
41
+ def milk
42
+ @cow = Cow.find(params[:id])
43
+ CowWorker.asynch_moo(:id => @cow.id)
44
+ end
45
+ end
46
+
47
+ Notice the `asynch_moo` call to `CowWorker`. This will call the `moo` method on the `CowWorker` in the background, passing any parameters you like on. In fact, workling will call whatever comes after asynch_ as a method on the worker instance.
48
+
49
+ ## Worker Lifecycle
50
+
51
+ All worker classes must inherit from this class, and be saved in `app/workers`. The Worker is loaded once, at which point the instance method `create` is called.
52
+
53
+ Calling `async_my_method` on the worker class will trigger background work. This means that the loaded Worker instance will receive a call to the method `my_method(:uid => "thisjobsuid2348732947923")`.
54
+
55
+ ## Exception handling in Workers
56
+
57
+ If an exception is raised in your Worker, it will not be propagated to the calling code by workling. This is because the code is called asynchronously, meaning that exceptions may be raised after the calling code has already returned. If you need your calling code to handle exceptional situations, you have to pass the error into the return store.
58
+
59
+ Workling does log all exceptions that propagate out of the worker methods.
60
+
61
+ ## Logging with Workling
62
+
63
+ `RAILS_DEFAULT_LOGGER` is available in all workers. Workers also have a logger method which returns the default logger, so you can log like this:
64
+
65
+ logger.info("about to moo.")
66
+
67
+ ## What should I know about the Spawn Runner?
68
+
69
+ Workling automatically detects and uses Spawn, if installed. Spawn basically forks Rails every time you invoke a workling. To see what sort of characteristics this has, go into script/console, and run this:
70
+
71
+ >> fork { sleep 100 }
72
+ => 1060 (the pid is returned)
73
+
74
+ You'll see that this executes pretty much instantly. Run 'top' in another terminal window, and look for the new ruby process. This might be around 30 MB. This tells you that using spawn as a runner will result low latency, but will take at least 30MB for each request you make.
75
+
76
+ You cannot run your workers on a remote machine or cluster them with spawn. You also have no persistence: if you've fired of a lot of work and everything dies, there's no way of picking up where you left off.
77
+
78
+ # Using the Starling runner
79
+
80
+ If you want cross machine jobs with low latency and a low memory overhead, you might want to look into using the Starling Runner.
81
+
82
+ ## Installing Starling
83
+
84
+ As of 27. September 2008, the recommended Starling setup is as follows:
85
+
86
+ gem sources -a http://gems.github.com/
87
+ sudo gem install starling-starling
88
+ mkdir /var/spool/starling
89
+
90
+ The robot Co-Op Memcached Gem version 1.5.0 has several bugs, which have been fixed in the fiveruns-memcache-client gem. The starling-starling gem will install this as a dependency. Refer to the fiveruns README to see what the exact fixes are.
91
+
92
+ The Rubyforge Starling gem is also out of date. Currently, the most authorative Project is starling-starling on github (27. September 2008).
93
+
94
+ Workling will now automatically detect and use Starling, unless you have also installed Spawn. If you have Spawn installed, you need to tell Workling to use Starling by putting this in your environment.rb:
95
+
96
+ Workling::Remote.dispatcher = Workling::Remote::Runners::StarlingRunner.new
97
+
98
+ ## Starting up the required processes
99
+
100
+ Here's what you need to get up and started in development mode. Look in config/workling.yml to see what the default ports are for other environments.
101
+
102
+ sudo starling -d -p 22122
103
+ script/workling_client start
104
+
105
+ ## Configuring workling.yml
106
+
107
+ Workling copies a file called workling.yml into your applications config directory. The config file tells Workling on which port Starling is listening.
108
+
109
+ Notice that the default production port is 15151. This means you'll need to start Starling with -p 15151 on production.
110
+
111
+ You can also use this config file to pass configuration options to the memcache client which workling uses to connect to starling. use the key 'memcache_options' for this.
112
+
113
+ You can also set sleep time for each Worker. See the key 'listeners' for this. Put in the modularized Class name as a key.
114
+
115
+ development:
116
+ listens_on: localhost:22122
117
+ sleep_time: 2
118
+ reset_time: 30
119
+ listeners:
120
+ Util:
121
+ sleep_time: 20
122
+ memcache_options:
123
+ namespace: myapp_development
124
+
125
+ production:
126
+ listens_on: localhost:22122, localhost:221223, localhost:221224
127
+ sleep_time: 2
128
+ reset_time: 30
129
+
130
+ Note that you can cluster Starling instances by passing a comma separated list of values to
131
+
132
+ Sleep time determines the wait time between polls against polls. A single poll will do one .get on every queue (there is a corresponding queue for each worker method).
133
+
134
+ If there is a memcache error, the Poller will hang for a bit to give it a chance to fire up again and reset the connection. The wait time can be set with the key reset_time.
135
+
136
+ ## Seeing what Starling is doing
137
+
138
+ Starling comes with it's own script, starling_top. If you want statistics specific to workling, run:
139
+
140
+ script/starling_status.rb
141
+
142
+ ## A Quick Starling Primer
143
+
144
+ You might wonder what exactly starling does. Here's a little snippet you can play with to illustrate how it works:
145
+
146
+ 4 # Put messages onto a queue:
147
+ 5 require 'memcache'
148
+ 6 starling = MemCache.new('localhost:22122')
149
+ 7 starling.set('my_queue', 1)
150
+ 8
151
+ 9 # Get messages from the queue:
152
+ 10 require 'memcache'
153
+ 11 starling = MemCache.new('localhost:22122')
154
+ 12 loop { puts starling.get('my_queue') }
155
+ 13
156
+
157
+ # Using RabbitMQ or any Queue Server that supports AMQP
158
+
159
+ RabbitMQ is a reliable, high performance queue server written in erlang. If you're doing high volume messaging and need a high degree of reliability, you should definitely consider using RabbitMQ over Starling.
160
+
161
+ A lot of Ruby people have been talking about using RabbitMQ as their Queue of choice. Soundcloud.com are using it, as is new bamboo founder Johnathan Conway, who is using it at his video startup http://www.vzaar.com/. He says:
162
+
163
+ > RabbitMQ – Now this is the matrons knockers when it comes to kick ass, ultra fast and scalable messaging. It simply rocks, with performance off the hook. It’s written in Erlang and supports the AMPQ protocol.
164
+
165
+ If you're on OSX, you can get started with RabbitMQ by following the installation instructions [here](http://playtype.net/past/2008/10/9/installing_rabbitmq_on_osx/). To get an idea of how to directly connect to RabbitMQ using ruby, have a look at [this article](http://playtype.net/past/2008/10/10/kickass_queuing_over_ruby_using_amqp).
166
+
167
+ Once you've installed RabbitMQ, install the ruby amqp library:
168
+
169
+ gem sources -a http://gems.github.com/ (if necessary)
170
+ sudo gem install tmm1-amqp
171
+
172
+ then configure configure your application to use Amqp by adding this:
173
+
174
+ Workling::Remote.invoker = Workling::Remote::Invokers::EventmachineSubscriber
175
+ Workling::Remote.dispatcher = Workling::Remote::Runners::ClientRunner.new
176
+ Workling::Remote.dispatcher.client = Workling::Clients::AmqpClient.new
177
+
178
+ Then start the workling Client:
179
+
180
+ 1 ./script/workling_client start
181
+
182
+ You're good.
183
+
184
+ # Using RudeQueue
185
+
186
+ RudeQueue is a Starling-like Queue that runs on top of your database and requires no extra processes. Use this if you don't need very fast job processing and want to avoid managing the extra process starling requires.
187
+
188
+ Install the RudeQ plugin like this:
189
+
190
+ 1 ./script/plugin install git://github.com/matthewrudy/rudeq.git
191
+ 2 rake queue:setup
192
+ 3 rake db:migrate
193
+
194
+ Configure Workling to use RudeQ. Add this to your environment:
195
+
196
+ Workling::Clients::MemcacheQueueClient.memcache_client_class = RudeQ::Client
197
+ Workling::Remote.dispatcher = Workling::Remote::Runners::ClientRunner.new
198
+
199
+ Now start the Workling Client:
200
+
201
+ 1 ./script/workling_client start
202
+
203
+ You're good.
204
+
205
+ # Using BackgroundJob
206
+
207
+ If you don't want to bother with seperate processes, are not worried about latence or memory footprint, then you might want to use Bj to power workling.
208
+
209
+ Install the Bj plugin like this:
210
+
211
+ 1 ./script/plugin install http://codeforpeople.rubyforge.org/svn/rails/plugins/bj
212
+ 2 ./script/bj setup
213
+
214
+ Workling will now automatically detect and use Bj, unless you have also installed Starling. If you have Starling installed, you need to tell Workling to use Bj by putting this in your environment.rb:
215
+
216
+ Workling::Remote.dispatcher = Workling::Remote::Runners::BackgroundjobRunner.new
217
+
218
+ # Progress indicators and return stores
219
+
220
+ Your worklings can write back to a return store. This allows you to write progress indicators, or access results from your workling. As above, this is fairly slim. Again, you can swap in any return store implementation you like without changing your code. They all behave like memcached. For tests, there is a memory return store, for production use there is currently a starling return store. You can easily add a new return store (over the database for instance) by subclassing `Workling::Return::Store::Base`. Configure it like this in your test environment:
221
+
222
+ Workling::Return::Store.instance = Workling::Return::Store::MemoryReturnStore.new
223
+
224
+ Setting and getting values works as follows. Read the next paragraph to see where the job-id comes from.
225
+
226
+ Workling.return.set("job-id-1", "moo")
227
+ Workling.return.get("job-id-1") => "moo"
228
+
229
+ Here is an example worker that crawls an addressbook and puts results into a return store. Workling makes sure you have a :uid in your argument hash - set the value into the return store using this uid as a key:
230
+
231
+ require 'blackbook'
232
+ class NetworkWorker < Workling::Base
233
+ def search(options)
234
+ results = Blackbook.get(options[:key], options[:username], options[:password])
235
+ Workling.return.set(options[:uid], results)
236
+ end
237
+ end
238
+
239
+ call your workling as above:
240
+
241
+ @uid = NetworkWorker.asynch_search(:key => :gmail, :username => "foo@gmail.com", :password => "bar")
242
+
243
+ you can now use the @uid to query the return store:
244
+
245
+ results = Workling.return.get(@uid)
246
+
247
+ of course, you can use this for progress indicators. just put the progress into the return store.
248
+
249
+ enjoy!
250
+
251
+ ## Adding new work brokers to Workling
252
+
253
+ There are two new base classes you can extend to add new brokers. I'll describe how this is done usin amqp as an example. The code i show is already a part of workling.
254
+
255
+ ### Clients
256
+
257
+ Clients help workling to connect to job brokers. To add an AmqpClient, we need to extend from `Workling::Client::Base` and implement a couple of methods.
258
+
259
+ require 'workling/clients/base'
260
+ require 'mq'
261
+
262
+ #
263
+ # An Ampq client
264
+ #
265
+ module Workling
266
+ module Clients
267
+ class AmqpClient < Workling::Clients::Base
268
+
269
+ # starts the client.
270
+ def connect
271
+ @amq = MQ.new
272
+ end
273
+
274
+ # stops the client.
275
+ def close
276
+ @amq.close
277
+ end
278
+
279
+ # request work
280
+ def request(queue, value)
281
+ @amq.queue(queue).publish(value)
282
+ end
283
+
284
+ # retrieve work
285
+ def retrieve(queue)
286
+ @amq.queue(queue)
287
+ end
288
+
289
+ # subscribe to a queue
290
+ def subscribe(queue)
291
+ @amq.queue(queue).subscribe do |value|
292
+ yield value
293
+ end
294
+ end
295
+
296
+ end
297
+ end
298
+ end
299
+
300
+ Were's using the eventmachine amqp client for this, you can find it [up on github](http://github.com/tmm1/amqp/tree/master). `connect` and `close` do exactly what it says on the tin: connecting to rabbitmq and closing the connection.
301
+
302
+ `request` and `retrieve` are responsible for placing work on rabbitmq. The methods are passed the correct queue, and a value that contains the worker method arguments. If you need control over the queue names, look at the RDoc for Workling::Routing::Base. In our case, there's no special requirement here.
303
+
304
+ Finally, we implement a `subscribe` method. Use this if your broker supports callbacks, as is the case with amqp. This method expects to a block, which we pass into the amqp subscribe method here. The block will be called when a message is available on the queue, and the result is yielded into the block.
305
+
306
+ Having subscription callbacks is very nice, because this way, we don't need to keep calling `get` on the queue to see if something new is waiting.
307
+
308
+ So now we're done! That's all you need to add RabbitMQ to workling. Configure it in your application as descibed below.
309
+
310
+ ### Invokers
311
+
312
+ There's still potential to improve things though. Workling 0.4.0 introduces the idea of invokers. Invokers grab work off a job broker, using a client (see above). They subclass Workling::Remote::Invokers::Base. Read the RDoc for a description of the methods.
313
+
314
+ Workling comes with a couple of standard invokers, like the BasicPoller. This invoker simply keeps hitting the broker every n seconds, checking for new work and executing it immediately. The ThreadedInvoker does the same, but spawns a Thread for every Worker class the project defines.
315
+
316
+ So Amqp: it would be nice if we had an invoker that makes use of the subscription callbacks. Easily done, lets have a look:
317
+
318
+ require 'eventmachine'
319
+ require 'workling/remote/invokers/base'
320
+
321
+ #
322
+ # Subscribes the workers to the correct queues.
323
+ #
324
+ module Workling
325
+ module Remote
326
+ module Invokers
327
+ class EventmachineSubscriber < Workling::Remote::Invokers::Base
328
+
329
+ def initialize(routing, client_class)
330
+ super
331
+ end
332
+
333
+ #
334
+ # Starts EM loop and sets up subscription callbacks for workers.
335
+ #
336
+ def listen
337
+ EM.run do
338
+ connect do
339
+ routes.each do |queue|
340
+ @client.subscribe(queue) do |args|
341
+ run(queue, args)
342
+ end
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ def stop
349
+ EM.stop if EM.reactor_running?
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end
355
+
356
+ Invokers have to implement two methods, `listen` and `stop`. Listen starts the main listener loop, which is responsible for starting work when it becomes available.
357
+
358
+ In our case, we need to start an EM loop around `listen`. This is because the Ruby AMQP library needs to run inside of an eventmachine reactor loop.
359
+
360
+ Next, inside of `listen`, we need to iterate through all defined routes. There is a route for each worker method you defined in your application. The routes double as queue names. For this, you can use the helper method `routes`. Now we attach a callback to each queue. We can use the helper method `run`, which executes the worker method associated with the queue, passing along any supplied arguments.
361
+
362
+ That's it! We now have a more effective Invoker.
363
+
364
+ # Contributors
365
+
366
+ The following people contributed code to workling so far. Many thanks :) If I forgot anybody, I aplogise. Just drop me a note and I'll add you to the project so that you can amend this!
367
+
368
+ Anybody who contributes fixes (with tests), or new functionality (whith tests) which is pulled into the main project, will also be be added to the project.
369
+
370
+ * Andrew Carter (ascarter)
371
+ * Chris Gaffney (gaffneyc)
372
+ * Matthew Rudy (matthewrudy)
373
+ * Larry Diehl (reeze)
374
+ * grantr (francios)
375
+ * David (digitalronin)
376
+ * Dave Dupré
377
+ * Douglas Shearer (dougal)
378
+ * Nick Plante (zapnap)
379
+ * Brent
380
+ * Evan Light (elight)
381
+
382
+ Copyright (c) 2008 play/type GmbH, released under the MIT license
@@ -0,0 +1,11 @@
1
+ #
2
+ # A RudeQ client that behvaes somewhat like memcache-client
3
+ #
4
+ module RudeQ
5
+ class Client
6
+ def initialize(*args); super(); end
7
+ def set(key, value); RudeQueue.set(key, value); end;
8
+ def get(key); RudeQueue.get(key); end;
9
+ def stats; ActiveRecord::Base.connection; end
10
+ end
11
+ end
@@ -0,0 +1,150 @@
1
+ #
2
+ # I can haz am in your Workling are belong to us!
3
+ #
4
+ module Workling
5
+ class WorklingError < StandardError; end
6
+ class WorklingNotFoundError < WorklingError; end
7
+ class WorklingConnectionError < WorklingError; end
8
+ class QueueserverNotFoundError < WorklingError
9
+ def initialize
10
+ super "config/workling.yml configured to connect to queue server on #{ Workling.config[:listens_on] } for this environment. could not connect to queue server on this host:port. for starling users: pass starling the port with -p flag when starting it. If you don't want to use Starling, then explicitly set Workling::Remote.dispatcher (see README for an example)"
11
+ end
12
+ end
13
+
14
+ class ConfigurationError < WorklingError
15
+ def initialize
16
+ super File.exist?(File.join(RAILS_ROOT, 'config', 'starling.yml')) ?
17
+ "config/starling.yml has been depracated. rename your config file to config/workling.yml then try again!" :
18
+ "config/workling.yml could not be loaded. check out README.markdown to see what this file should contain. "
19
+ end
20
+ end
21
+
22
+ mattr_accessor :load_path
23
+ @@load_path = [ File.expand_path(File.join(File.dirname(__FILE__), '../../../../app/workers')) ]
24
+ VERSION = "0.4.2.3"
25
+
26
+ #
27
+ # determine the runner to use if nothing is specifically set. workling will try to detect
28
+ # starling, spawn, or bj, in that order. if none of these are found, notremoterunner will
29
+ # be used.
30
+ #
31
+ # this can be overridden by setting Workling::Remote.dispatcher, eg:
32
+ # Workling::Remote.dispatcher = Workling::Remote::Runners::StarlingRunner.new
33
+ #
34
+ def self.default_runner
35
+ if RAILS_ENV == "test"
36
+ Workling::Remote::Runners::NotRemoteRunner.new
37
+ elsif starling_installed?
38
+ Workling::Remote::Runners::StarlingRunner.new
39
+ elsif spawn_installed?
40
+ Workling::Remote::Runners::SpawnRunner.new
41
+ elsif bj_installed?
42
+ Workling::Remote::Runners::BackgroundjobRunner.new
43
+ else
44
+ Workling::Remote::Runners::NotRemoteRunner.new
45
+ end
46
+ end
47
+
48
+ #
49
+ # gets the worker instance, given a class. the optional method argument will cause an
50
+ # exception to be raised if the worker instance does not respoind to said method.
51
+ #
52
+ def self.find(clazz, method = nil)
53
+ begin
54
+ inst = clazz.to_s.camelize.constantize.new
55
+ rescue NameError
56
+ raise_not_found(clazz, method)
57
+ end
58
+ raise_not_found(clazz, method) if method && !inst.respond_to?(method)
59
+ inst
60
+ end
61
+
62
+ # returns Workling::Return::Store.instance.
63
+ def self.return
64
+ Workling::Return::Store.instance
65
+ end
66
+
67
+ # is spawn installed?
68
+ def self.spawn_installed?
69
+ begin
70
+ require 'spawn'
71
+ rescue LoadError
72
+ end
73
+
74
+ Object.const_defined? "Spawn"
75
+ end
76
+
77
+ # is starling installed?
78
+ def self.starling_installed?
79
+ begin
80
+ require 'starling'
81
+ rescue LoadError
82
+ end
83
+
84
+ Object.const_defined? "Starling"
85
+ end
86
+
87
+ # is bj installed?
88
+ def self.bj_installed?
89
+ Object.const_defined? "Bj"
90
+ end
91
+
92
+ # tries to load fiveruns-memcache-client. if this isn't found,
93
+ # memcache-client is searched for. if that isn't found, don't do anything.
94
+ def self.try_load_a_memcache_client
95
+ begin
96
+ gem 'fiveruns-memcache-client'
97
+ require 'memcache'
98
+ rescue Gem::LoadError
99
+ begin
100
+ gem 'memcache-client'
101
+ require 'memcache'
102
+ rescue Gem::LoadError
103
+ Workling::Base.logger.info "WORKLING: couldn't find a memcache client - you need one for the starling runner. "
104
+ end
105
+ end
106
+ end
107
+
108
+ # attempts to load amqp and writes out descriptive error message if not present
109
+ def self.try_load_an_amqp_client
110
+ begin
111
+ require 'mq'
112
+ rescue Exception => e
113
+ raise WorklingError.new(
114
+ "WORKLING: couldn't find the ruby amqp client - you need it for the amqp runner. " \
115
+ "Install from github: gem sources -a http://gems.github.com/ && sudo gem install tmm1-amqp "
116
+ )
117
+ end
118
+ end
119
+
120
+ #
121
+ # returns a config hash. reads RAILS_ROOT/config/workling.yml
122
+ #
123
+ def self.config
124
+ begin
125
+ config_path = File.join(RAILS_ROOT, 'config', 'workling.yml')
126
+ @@config ||= YAML.load_file(config_path)[RAILS_ENV || 'development'].symbolize_keys
127
+ @@config[:memcache_options].symbolize_keys! if @@config[:memcache_options]
128
+ @@config
129
+ rescue
130
+ # config files could not be read correctly
131
+ raise ConfigurationError.new
132
+ end
133
+ end
134
+
135
+ #
136
+ # Raises exceptions thrown inside of the worker. normally, these are logged to
137
+ # logger.error. it's easy to miss these log calls while developing, though.
138
+ #
139
+ mattr_accessor :raise_exceptions
140
+ @@raise_exceptions = (RAILS_ENV == "test" || RAILS_ENV == "development")
141
+
142
+ def self.raise_exceptions?
143
+ @@raise_exceptions
144
+ end
145
+
146
+ private
147
+ def self.raise_not_found(clazz, method)
148
+ raise Workling::WorklingNotFoundError.new("could not find #{ clazz }:#{ method } workling. ")
149
+ end
150
+ end