elecnix-workling 0.4.2 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. data/CHANGES.markdown +10 -0
  2. data/README.markdown +161 -4
  3. data/bin/workling_client +28 -0
  4. metadata +3 -75
  5. data/lib/rude_q/client.rb +0 -11
  6. data/lib/workling.rb +0 -150
  7. data/lib/workling/base.rb +0 -71
  8. data/lib/workling/clients/amqp_client.rb +0 -56
  9. data/lib/workling/clients/base.rb +0 -57
  10. data/lib/workling/clients/memcache_queue_client.rb +0 -83
  11. data/lib/workling/discovery.rb +0 -14
  12. data/lib/workling/remote.rb +0 -42
  13. data/lib/workling/remote/invokers/base.rb +0 -124
  14. data/lib/workling/remote/invokers/basic_poller.rb +0 -41
  15. data/lib/workling/remote/invokers/eventmachine_subscriber.rb +0 -41
  16. data/lib/workling/remote/invokers/threaded_poller.rb +0 -140
  17. data/lib/workling/remote/runners/backgroundjob_runner.rb +0 -35
  18. data/lib/workling/remote/runners/base.rb +0 -42
  19. data/lib/workling/remote/runners/client_runner.rb +0 -45
  20. data/lib/workling/remote/runners/not_remote_runner.rb +0 -23
  21. data/lib/workling/remote/runners/rudeq_runner.rb +0 -23
  22. data/lib/workling/remote/runners/spawn_runner.rb +0 -38
  23. data/lib/workling/remote/runners/starling_runner.rb +0 -13
  24. data/lib/workling/return/store/base.rb +0 -42
  25. data/lib/workling/return/store/iterator.rb +0 -24
  26. data/lib/workling/return/store/memory_return_store.rb +0 -26
  27. data/lib/workling/return/store/rudeq_return_store.rb +0 -24
  28. data/lib/workling/return/store/starling_return_store.rb +0 -31
  29. data/lib/workling/routing/base.rb +0 -13
  30. data/lib/workling/routing/class_and_method_routing.rb +0 -55
  31. data/lib/workling/rudeq.rb +0 -7
  32. data/lib/workling/rudeq/client.rb +0 -17
  33. data/lib/workling/rudeq/poller.rb +0 -116
  34. data/test/class_and_method_routing_test.rb +0 -18
  35. data/test/clients/memory_queue_client.rb +0 -36
  36. data/test/discovery_test.rb +0 -13
  37. data/test/invoker_basic_poller_test.rb +0 -29
  38. data/test/invoker_eventmachine_subscription_test.rb +0 -26
  39. data/test/invoker_threaded_poller_test.rb +0 -34
  40. data/test/memcachequeue_client_test.rb +0 -36
  41. data/test/memory_return_store_test.rb +0 -32
  42. data/test/mocks/client.rb +0 -9
  43. data/test/mocks/logger.rb +0 -5
  44. data/test/mocks/rude_queue.rb +0 -9
  45. data/test/mocks/spawn.rb +0 -5
  46. data/test/not_remote_runner_test.rb +0 -11
  47. data/test/remote_runner_test.rb +0 -58
  48. data/test/rescue_test.rb +0 -24
  49. data/test/return_store_test.rb +0 -24
  50. data/test/rudeq_client_test.rb +0 -30
  51. data/test/rudeq_poller_test.rb +0 -14
  52. data/test/rudeq_return_store_test.rb +0 -20
  53. data/test/rudeq_runner_test.rb +0 -22
  54. data/test/runners/thread_runner.rb +0 -22
  55. data/test/spawn_runner_test.rb +0 -10
  56. data/test/starling_return_store_test.rb +0 -29
  57. data/test/starling_runner_test.rb +0 -8
  58. data/test/test_helper.rb +0 -50
  59. data/test/workers/analytics/invites.rb +0 -10
  60. data/test/workers/util.rb +0 -25
data/lib/workling/base.rb DELETED
@@ -1,71 +0,0 @@
1
- #require 'struct'
2
-
3
- #
4
- # All worker classes must inherit from this class, and be saved in app/workers.
5
- #
6
- # The Worker lifecycle:
7
- # The Worker is loaded once, at which point the instance method 'create' is called.
8
- #
9
- # Invoking Workers:
10
- # Calling async_my_method on the worker class will trigger background work.
11
- # This means that the loaded Worker instance will receive a call to the method
12
- # my_method(:uid => "thisjobsuid2348732947923").
13
- #
14
- # The Worker method must have a single hash argument. Note that the job :uid will
15
- # be merged into the hash.
16
- #
17
- module Workling
18
- class Base
19
- cattr_accessor :logger
20
- @@logger ||= ::RAILS_DEFAULT_LOGGER
21
-
22
- def self.inherited(subclass)
23
- Workling::Discovery.discovered << subclass
24
- end
25
-
26
- def initialize
27
- super
28
-
29
- create
30
- end
31
-
32
- # Put worker initialization code in here. This is good for restarting jobs that
33
- # were interrupted.
34
- def create
35
- end
36
-
37
- # takes care of suppressing remote errors but raising Workling::WorklingNotFoundError
38
- # where appropriate. swallow workling exceptions so that everything behaves like remote code.
39
- # otherwise StarlingRunner and SpawnRunner would behave too differently to NotRemoteRunner.
40
- def dispatch_to_worker_method(method, options = {})
41
- begin
42
- options = default_options.merge(options)
43
- self.send(method, options)
44
- rescue Workling::WorklingError => e
45
- raise e
46
- rescue Exception => e
47
- raise e if e.kind_of? Workling::WorklingError
48
- logger.error "WORKLING ERROR: runner could not invoke #{ self.class }:#{ method } with #{ options.inspect }. error was: #{ e.class }:#{ e.message }\n #{ e.backtrace.join("\n") }"
49
-
50
- on_error(e) if respond_to?(:on_error)
51
-
52
- # reraise after logging. the exception really can't go anywhere in many cases. (spawn traps the exception)
53
- raise e if Workling.raise_exceptions?
54
- end
55
- end
56
-
57
- # supply default_options as a hash in classes that inherit Workling::Base
58
- def default_options
59
- {}
60
- end
61
-
62
- # thanks to blaine cook for this suggestion.
63
- def self.method_missing(method, *args, &block)
64
- if method.to_s =~ /^asynch?_(.*)/
65
- Workling::Remote.run(self.to_s.dasherize, $1, *args)
66
- else
67
- super
68
- end
69
- end
70
- end
71
- end
@@ -1,56 +0,0 @@
1
- require 'workling/clients/base'
2
- Workling.try_load_an_amqp_client
3
-
4
- #
5
- # An Ampq client
6
- #
7
- module Workling
8
- module Clients
9
- class AmqpClient < Workling::Clients::Base
10
-
11
- # starts the client.
12
- def connect
13
- begin
14
- @options = (Workling.config[:amqp_options] || {}).symbolize_keys
15
- # host and port only needed for AMQP.start
16
- host, port = Workling.config[:listens_on].split(':', 2)
17
- start_opts = {:host => host || 'localhost', :port => (port || 5672).to_i}.merge(@options)
18
- @amq = MQ.new(AMQP.start(start_opts))
19
- rescue
20
- raise WorklingError.new("couldn't start amq client. if you're running this in a server environment, then make sure the server is evented (ie use thin or evented mongrel, not normal mongrel.): #{$!}")
21
- end
22
- end
23
-
24
- # no need for explicit closing. when the event loop
25
- # terminates, the connection is closed anyway.
26
- def close; true; end
27
-
28
- # Decide which method of marshalling to use:
29
- def cast_value(value, origin)
30
- case Workling.config[:ymj]
31
- when 'marshal'
32
- @cast_value = origin == :request ? Marshal.dump(value) : Marshal.load(value)
33
- when 'yaml'
34
- @cast_value = origin == :request ? YAML.dump(value) : YAML.load(value)
35
- else
36
- @cast_value = origin == :request ? Marshal.dump(value) : Marshal.load(value)
37
- end
38
- @cast_value
39
- end
40
-
41
- # subscribe to a queue
42
- def subscribe(key)
43
- @amq.queue(key, @options).subscribe(@options) do |value|
44
- yield YAML.load(value, :subscribe)
45
- end
46
- end
47
-
48
- # request and retrieve work
49
- def retrieve(key); @amq.queue(key, @options); end
50
- def request(key, value)
51
- logger.info("> publishing to #{key}: #{value.inspect}")
52
- @amq.queue(key, @options).publish(cast_value(value, :request), @options)
53
- end
54
- end
55
- end
56
- end
@@ -1,57 +0,0 @@
1
- #
2
- # Clients are responsible for communicating with a job broker (ie connecting to starling or rabbitmq.)
3
- #
4
- # Clients are used to request jobs on a broker, get results for a job from a broker, and subscribe to results
5
- # from a specific type of job.
6
- #
7
- module Workling
8
- module Clients
9
- class Base
10
-
11
- # returns the Workling::Base.logger
12
- def logger; Workling::Base.logger; end
13
-
14
- #
15
- # Requests a job on the broker.
16
- #
17
- # work_type:
18
- # arguments: the argument to the worker method
19
- #
20
- def request(work_type, arguments)
21
- raise NotImplementedError.new("Implement request(work_type, arguments) in your client. ")
22
- end
23
-
24
- #
25
- # Gets job results off a job broker. Returns nil if there are no results.
26
- #
27
- # worker_uid: the uid returned by workling when the work was dispatched
28
- #
29
- def retrieve(work_uid)
30
- raise NotImplementedError.new("Implement retrieve(work_uid) in your client. ")
31
- end
32
-
33
- #
34
- # Subscribe to job results in a job broker.
35
- #
36
- # worker_type:
37
- #
38
- def subscribe(work_type)
39
- raise NotImplementedError.new("Implement subscribe(work_type) in your client. ")
40
- end
41
-
42
- #
43
- # Opens a connection to the job broker.
44
- #
45
- def connect
46
- raise NotImplementedError.new("Implement connect() in your client. ")
47
- end
48
-
49
- #
50
- # Closes the connection to the job broker.
51
- #
52
- def close
53
- raise NotImplementedError.new("Implement close() in your client. ")
54
- end
55
- end
56
- end
57
- end
@@ -1,83 +0,0 @@
1
- require 'workling/clients/base'
2
- require 'memcache'
3
-
4
- #
5
- # This client can be used for all Queue Servers that speak Memcached, such as Starling.
6
- #
7
- # Wrapper for the memcache connection. The connection is made using fiveruns-memcache-client,
8
- # or memcache-client, if this is not available. See the README for a discussion of the memcache
9
- # clients.
10
- #
11
- # method_missing delegates all messages through to the underlying memcache connection.
12
- #
13
- module Workling
14
- module Clients
15
- class MemcacheQueueClient < Workling::Clients::Base
16
-
17
- # the class with which the connection is instantiated
18
- cattr_accessor :memcache_client_class
19
- @@memcache_client_class ||= ::MemCache
20
-
21
- # the url with which the memcache client expects to reach starling
22
- attr_accessor :queueserver_urls
23
-
24
- # the memcache connection object
25
- attr_accessor :connection
26
-
27
- #
28
- # the client attempts to connect to queueserver using the configuration options found in
29
- #
30
- # Workling.config. this can be configured in config/workling.yml.
31
- #
32
- # the initialization code will raise an exception if memcache-client cannot connect
33
- # to queueserver.
34
- #
35
- def connect
36
- @queueserver_urls = Workling.config[:listens_on].split(',').map { |url| url ? url.strip : url }
37
- options = [@queueserver_urls, Workling.config[:memcache_options]].compact
38
- self.connection = MemcacheQueueClient.memcache_client_class.new(*options)
39
-
40
- raise_unless_connected!
41
- end
42
-
43
- # closes the memcache connection
44
- def close
45
- self.connection.flush_all
46
- self.connection.reset
47
- end
48
-
49
- # implements the client job request and retrieval
50
- def request(key, value)
51
- set(key, value)
52
- end
53
-
54
- def retrieve(key)
55
- begin
56
- get(key)
57
- rescue MemCache::MemCacheError => e
58
- # failed to enqueue, raise a workling error so that it propagates upwards
59
- raise Workling::WorklingError.new("#{e.class.to_s} - #{e.message}")
60
- end
61
- end
62
-
63
- private
64
- # make sure we can actually connect to queueserver on the given port
65
- def raise_unless_connected!
66
- begin
67
- self.connection.stats
68
- rescue
69
- raise Workling::QueueserverNotFoundError.new
70
- end
71
- end
72
-
73
- # delegates directly through to the memcache connection.
74
- def method_missing(method, *args)
75
- begin
76
- self.connection.send(method, *args)
77
- rescue MemCache::MemCacheError => e
78
- raise Workling::WorklingConnectionError.new("#{e.class.to_s} - #{e.message}")
79
- end
80
- end
81
- end
82
- end
83
- end
@@ -1,14 +0,0 @@
1
- #
2
- # Discovery is responsible for loading workers in app/workers.
3
- #
4
- module Workling
5
- class Discovery
6
- cattr_accessor :discovered
7
- @@discovered = []
8
-
9
- # requires worklings so that they are added to routing.
10
- def self.discover!
11
- Workling.load_path.map{|p| Dir.glob("#{p}/**/*.rb")}.flatten.each{|wling| require wling}
12
- end
13
- end
14
- end
@@ -1,42 +0,0 @@
1
- require "workling/remote/runners/not_remote_runner"
2
- require "workling/remote/runners/spawn_runner"
3
- require "workling/remote/runners/starling_runner"
4
- require "workling/remote/runners/backgroundjob_runner"
5
-
6
- require 'digest/md5'
7
-
8
- #
9
- # Scoping Module for Runners.
10
- #
11
- module Workling
12
- module Remote
13
-
14
- # set the desired runner here. this is initialized with Workling.default_runner.
15
- mattr_accessor :dispatcher
16
-
17
- # set the desired invoker. this class grabs work from the job broker and executes it.
18
- mattr_accessor :invoker
19
- @@invoker ||= Workling::Remote::Invokers::ThreadedPoller
20
-
21
- # retrieve the dispatcher or instantiate it using the defaults
22
- def self.dispatcher
23
- @@dispatcher ||= Workling.default_runner
24
- end
25
-
26
- # generates a unique identifier for this particular job.
27
- def self.generate_uid(clazz, method)
28
- uid = ::Digest::MD5.hexdigest("#{ clazz }:#{ method }:#{ rand(1 << 64) }:#{ Time.now }")
29
- "#{ clazz.to_s.tableize }/#{ method }/#{ uid }".split("/").join(":")
30
- end
31
-
32
- # dispatches to a workling. writes the :uid for this work into the options hash, so make
33
- # sure you pass in a hash if you want write to a return store in your workling.
34
- def self.run(clazz, method, options = {})
35
- uid = Workling::Remote.generate_uid(clazz, method)
36
- options[:uid] = uid if options.kind_of?(Hash) && !options[:uid]
37
- Workling.find(clazz, method) # this line raises a WorklingError if the method does not exist.
38
- dispatcher.run(clazz, method, options)
39
- uid
40
- end
41
- end
42
- end
@@ -1,124 +0,0 @@
1
- #
2
- # Invokers are responsible for
3
- #
4
- # 1. grabbing work off a job broker (such as a starling or rabbitmq server).
5
- # 2. routing (mapping) that work onto the correct worker method.
6
- # 3.invoking the worker method, passing any arguments that came off the broker.
7
- #
8
- # Invokers should implement their own concurrency strategies. For example,
9
- # The there is a ThreadedPoller which starts a thread for each Worker class.
10
- #
11
- # This base Invoker class defines the methods an Invoker needs to implement.
12
- #
13
- module Workling
14
- module Remote
15
- module Invokers
16
- class Base
17
-
18
- attr_accessor :sleep_time, :reset_time
19
-
20
- #
21
- # call up with super in the subclass constructor.
22
- #
23
- def initialize(routing, client_class)
24
- @routing = routing
25
- @client_class = client_class
26
- @sleep_time = Workling.config[:sleep_time] || 2
27
- @reset_time = Workling.config[:reset_time] || 30
28
- @@mutex ||= Mutex.new
29
- end
30
-
31
- #
32
- # Starts main Invoker Loop. The invoker runs until stop() is called.
33
- #
34
- def listen
35
- raise NotImplementedError.new("Implement listen() in your Invoker. ")
36
- end
37
-
38
- #
39
- # Gracefully stops the Invoker. The currently executing Jobs should be allowed
40
- # to finish.
41
- #
42
- def stop
43
- raise NotImplementedError.new("Implement stop() in your Invoker. ")
44
- end
45
-
46
- #
47
- # Runs the worker method, given
48
- #
49
- # type: the worker route
50
- # args: the arguments to be passed into the worker method.
51
- #
52
- def run(type, args)
53
- worker = @routing[type]
54
- method = @routing.method_name(type)
55
- worker.dispatch_to_worker_method(method, args)
56
- end
57
-
58
- # returns the Workling::Base.logger
59
- def logger; Workling::Base.logger; end
60
-
61
- protected
62
-
63
- # handle opening and closing of client. pass code block to this method.
64
- def connect
65
- @client = @client_class.new
66
- @client.connect
67
-
68
- begin
69
- yield
70
- ensure
71
- @client.close
72
- ActiveRecord::Base.verify_active_connections!
73
- end
74
- end
75
-
76
- #
77
- # Loops through the available routes, yielding for each route.
78
- # This continues until @shutdown is set on this instance.
79
- #
80
- def loop_routes
81
- while(!@shutdown) do
82
- ensure_activerecord_connection
83
-
84
- routes.each do |route|
85
- break if @shutdown
86
- yield route
87
- end
88
-
89
- sleep self.sleep_time
90
- end
91
- end
92
-
93
- #
94
- # Returns the complete set of active routes
95
- #
96
- def routes
97
- @active_routes ||= Workling::Discovery.discovered.map { |clazz| @routing.queue_names_routing_class(clazz) }.flatten
98
- end
99
-
100
- # Thanks for this Brent!
101
- #
102
- # ...Just a heads up, due to how rails’ MySQL adapter handles this
103
- # call ‘ActiveRecord::Base.connection.active?’, you’ll need
104
- # to wrap the code that checks for a connection in in a mutex.
105
- #
106
- # ....I noticed this while working with a multi-core machine that
107
- # was spawning multiple workling threads. Some of my workling
108
- # threads would hit serious issues at this block of code without
109
- # the mutex.
110
- #
111
- def ensure_activerecord_connection
112
- @@mutex.synchronize do
113
- unless ActiveRecord::Base.connection.active? # Keep MySQL connection alive
114
- unless ActiveRecord::Base.connection.reconnect!
115
- logger.fatal("Failed - Database not available!")
116
- break
117
- end
118
- end
119
- end
120
- end
121
- end
122
- end
123
- end
124
- end