workling 0.4.9.7
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.
- data/CHANGES.markdown +82 -0
- data/README.markdown +543 -0
- data/TODO.markdown +27 -0
- data/VERSION.yml +4 -0
- data/bin/workling_client +29 -0
- data/contrib/bj_invoker.rb +11 -0
- data/contrib/starling_status.rb +37 -0
- data/lib/extensions/cattr_accessor.rb +51 -0
- data/lib/extensions/mattr_accessor.rb +55 -0
- data/lib/workling.rb +213 -0
- data/lib/workling/base.rb +110 -0
- data/lib/workling/clients/amqp_client.rb +51 -0
- data/lib/workling/clients/amqp_exchange_client.rb +58 -0
- data/lib/workling/clients/backgroundjob_client.rb +25 -0
- data/lib/workling/clients/base.rb +89 -0
- data/lib/workling/clients/broker_base.rb +63 -0
- data/lib/workling/clients/memcache_queue_client.rb +104 -0
- data/lib/workling/clients/memory_queue_client.rb +34 -0
- data/lib/workling/clients/not_client.rb +14 -0
- data/lib/workling/clients/not_remote_client.rb +17 -0
- data/lib/workling/clients/rude_q_client.rb +47 -0
- data/lib/workling/clients/spawn_client.rb +46 -0
- data/lib/workling/clients/sqs_client.rb +163 -0
- data/lib/workling/clients/thread_client.rb +18 -0
- data/lib/workling/clients/xmpp_client.rb +110 -0
- data/lib/workling/discovery.rb +16 -0
- data/lib/workling/invokers/amqp_single_subscriber.rb +42 -0
- data/lib/workling/invokers/base.rb +124 -0
- data/lib/workling/invokers/basic_poller.rb +38 -0
- data/lib/workling/invokers/eventmachine_subscriber.rb +38 -0
- data/lib/workling/invokers/looped_subscriber.rb +34 -0
- data/lib/workling/invokers/thread_pool_poller.rb +165 -0
- data/lib/workling/invokers/threaded_poller.rb +149 -0
- data/lib/workling/remote.rb +38 -0
- data/lib/workling/return/store/base.rb +42 -0
- data/lib/workling/return/store/iterator.rb +24 -0
- data/lib/workling/return/store/memory_return_store.rb +24 -0
- data/lib/workling/return/store/starling_return_store.rb +30 -0
- data/lib/workling/routing/base.rb +13 -0
- data/lib/workling/routing/class_and_method_routing.rb +55 -0
- data/lib/workling/routing/static_routing.rb +43 -0
- data/lib/workling_daemon.rb +111 -0
- metadata +96 -0
data/TODO.markdown
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Todos for 0.5.0
|
2
|
+
|
3
|
+
* add a linting runner for tests. should check that no ar objects are being passed around
|
4
|
+
* add a mechanism for requiring models, for those people who insist on passing models across the wire
|
5
|
+
* add reloading of workers if Rails.reload?
|
6
|
+
* write some code that knows if the client should be started, and gives out a warning
|
7
|
+
* add a configuration option for SERVER/CLIENT
|
8
|
+
* add phusion daemon starter option so that workling_client doesn't need to be started manually on SERVER
|
9
|
+
* write some more documentation on the above issues and on standard remote setup.
|
10
|
+
* create a public forum, rdoc site
|
11
|
+
* try to reduce user error in setting environments correctly
|
12
|
+
* add beanstalkd runner
|
13
|
+
* refactor starling* to be memcache*. add aliased classes into deprecated.rb.
|
14
|
+
* look into create method. is this being called more often than intended?
|
15
|
+
* add some monit and god scripts as starters
|
16
|
+
* try to catch more user setup errors which lead to worker code not being called
|
17
|
+
* add json as a marshaling option for the amqp client.
|
18
|
+
|
19
|
+
# Todos for 1.0
|
20
|
+
|
21
|
+
* gemify
|
22
|
+
* move all runner/invoker implementations out of workling
|
23
|
+
* move backend discovery code out of workling
|
24
|
+
* decide on a single backend to include in workling
|
25
|
+
* merb support
|
26
|
+
* test on jruby
|
27
|
+
* more runners: sqs
|
data/VERSION.yml
ADDED
data/bin/workling_client
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'daemons'
|
4
|
+
require File.join(File.dirname(__FILE__), '../lib/workling_daemon')
|
5
|
+
|
6
|
+
daemon_options = {
|
7
|
+
:app_name => "workling",
|
8
|
+
:dir_mode => :normal,
|
9
|
+
:dir => File.join(Dir.pwd, 'log'),
|
10
|
+
:log_output => true,
|
11
|
+
:multiple => false,
|
12
|
+
:backtrace => true,
|
13
|
+
:monitor => false
|
14
|
+
}.merge(WorklingDaemon.parse_daemon_options(ARGV))
|
15
|
+
|
16
|
+
workling_options = {
|
17
|
+
:client_class => "memcache_queue",
|
18
|
+
:invoker_class => "threaded_poller",
|
19
|
+
:routing_class => "class_and_method",
|
20
|
+
:rails_root => Dir.pwd,
|
21
|
+
:load_path => ['app/workers/**/*.rb'],
|
22
|
+
:rails_env => (ENV['RAILS_ENV'] || "development").dup,
|
23
|
+
:no_rails => false
|
24
|
+
}.merge(WorklingDaemon.parse_workling_options(ARGV))
|
25
|
+
|
26
|
+
Daemons.run_proc(daemon_options[:app_name], daemon_options) do
|
27
|
+
Dir.chdir(workling_options[:rails_root])
|
28
|
+
WorklingDaemon.run(workling_options)
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
@routing = Workling::Routing::ClassAndMethodRouting.new
|
2
|
+
unnormalized = REXML::Text::unnormalize(STDIN.read)
|
3
|
+
message, command, args = *unnormalized.match(/(^[^ ]*) (.*)/)
|
4
|
+
options = Hash.from_xml(args)["hash"]
|
5
|
+
|
6
|
+
if workling = @routing[command]
|
7
|
+
options = options.symbolize_keys
|
8
|
+
method_name = @routing.method_name(command)
|
9
|
+
|
10
|
+
workling.dispatch_to_worker_method(method_name, options)
|
11
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
puts '=> Loading Rails...'
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/../config/environment'
|
6
|
+
require File.dirname(__FILE__) + '/../vendor/plugins/workling/lib/workling/remote/invokers/basic_poller'
|
7
|
+
require File.dirname(__FILE__) + '/../vendor/plugins/workling/lib/workling/routing/class_and_method_routing'
|
8
|
+
|
9
|
+
puts '** Rails loaded.'
|
10
|
+
|
11
|
+
trap(:INT) { exit }
|
12
|
+
|
13
|
+
client = Workling::Clients::MemcacheQueueClient.new
|
14
|
+
|
15
|
+
begin
|
16
|
+
client.connect
|
17
|
+
client.reset
|
18
|
+
|
19
|
+
client.stats # do this so that connection is shown as established below.
|
20
|
+
|
21
|
+
puts "Queue state:"
|
22
|
+
pp client.inspect
|
23
|
+
pp "Active?: #{client.active?}"
|
24
|
+
pp "Read Only?: #{client.readonly?}"
|
25
|
+
puts ""
|
26
|
+
puts "Servers:"
|
27
|
+
pp client.servers
|
28
|
+
puts ""
|
29
|
+
puts "Queue stats:"
|
30
|
+
pp client.stats
|
31
|
+
|
32
|
+
puts "\nThread Stats:"
|
33
|
+
pp Thread.list
|
34
|
+
ensure
|
35
|
+
puts '** Exiting'
|
36
|
+
client.close
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Extends the class object with class and instance accessors for class attributes,
|
2
|
+
# just like the native attr* accessors for instance attributes.
|
3
|
+
#
|
4
|
+
# class Person
|
5
|
+
# cattr_accessor :hair_colors
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
# Person.hair_colors = [:brown, :black, :blonde, :red]
|
9
|
+
class Class
|
10
|
+
def cattr_reader(*syms)
|
11
|
+
syms.flatten.each do |sym|
|
12
|
+
next if sym.is_a?(Hash)
|
13
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
14
|
+
unless defined? @@#{sym} # unless defined? @@hair_colors
|
15
|
+
@@#{sym} = nil # @@hair_colors = nil
|
16
|
+
end # end
|
17
|
+
#
|
18
|
+
def self.#{sym} # def self.hair_colors
|
19
|
+
@@#{sym} # @@hair_colors
|
20
|
+
end # end
|
21
|
+
#
|
22
|
+
def #{sym} # def hair_colors
|
23
|
+
@@#{sym} # @@hair_colors
|
24
|
+
end # end
|
25
|
+
EOS
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def cattr_writer(*syms)
|
30
|
+
syms.flatten.each do |sym|
|
31
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
32
|
+
unless defined? @@#{sym} # unless defined? @@hair_colors
|
33
|
+
@@#{sym} = nil # @@hair_colors = nil
|
34
|
+
end # end
|
35
|
+
#
|
36
|
+
def self.#{sym}=(obj) # def self.hair_colors=(obj)
|
37
|
+
@@#{sym} = obj # @@hair_colors = obj
|
38
|
+
end # end
|
39
|
+
#
|
40
|
+
def #{sym}=(obj) # def hair_colors=(obj)
|
41
|
+
@@#{sym} = obj # @@hair_colors = obj
|
42
|
+
end # end
|
43
|
+
EOS
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def cattr_accessor(*syms)
|
48
|
+
cattr_reader(*syms)
|
49
|
+
cattr_writer(*syms)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Extends the module object with module and instance accessors for class attributes,
|
2
|
+
# just like the native attr* accessors for instance attributes.
|
3
|
+
#
|
4
|
+
# module AppConfiguration
|
5
|
+
# mattr_accessor :google_api_key
|
6
|
+
# self.google_api_key = "123456789"
|
7
|
+
#
|
8
|
+
# mattr_accessor :paypal_url
|
9
|
+
# self.paypal_url = "www.sandbox.paypal.com"
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# AppConfiguration.google_api_key = "overriding the api key!"
|
13
|
+
class Module
|
14
|
+
def mattr_reader(*syms)
|
15
|
+
syms.each do |sym|
|
16
|
+
next if sym.is_a?(Hash)
|
17
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
18
|
+
unless defined? @@#{sym} # unless defined? @@pagination_options
|
19
|
+
@@#{sym} = nil # @@pagination_options = nil
|
20
|
+
end # end
|
21
|
+
#
|
22
|
+
def self.#{sym} # def self.pagination_options
|
23
|
+
@@#{sym} # @@pagination_options
|
24
|
+
end # end
|
25
|
+
#
|
26
|
+
def #{sym} # def pagination_options
|
27
|
+
@@#{sym} # @@pagination_options
|
28
|
+
end # end
|
29
|
+
EOS
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def mattr_writer(*syms)
|
34
|
+
syms.each do |sym|
|
35
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
36
|
+
unless defined? @@#{sym} # unless defined? @@pagination_options
|
37
|
+
@@#{sym} = nil # @@pagination_options = nil
|
38
|
+
end # end
|
39
|
+
#
|
40
|
+
def self.#{sym}=(obj) # def self.pagination_options=(obj)
|
41
|
+
@@#{sym} = obj # @@pagination_options = obj
|
42
|
+
end # end
|
43
|
+
#
|
44
|
+
def #{sym}=(obj) # def pagination_options=(obj)
|
45
|
+
@@#{sym} = obj # @@pagination_options = obj
|
46
|
+
end # end
|
47
|
+
EOS
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def mattr_accessor(*syms)
|
52
|
+
mattr_reader(*syms)
|
53
|
+
mattr_writer(*syms)
|
54
|
+
end
|
55
|
+
end
|
data/lib/workling.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
#
|
2
|
+
# I can haz am in your Workling are belong to us!
|
3
|
+
#
|
4
|
+
def require_in_tree(name)
|
5
|
+
require File.join(File.dirname(__FILE__), name)
|
6
|
+
end
|
7
|
+
|
8
|
+
require_in_tree 'extensions/mattr_accessor' unless Module.respond_to?(:mattr_accessor)
|
9
|
+
require_in_tree 'extensions/cattr_accessor' unless Class.respond_to?(:cattr_accessor)
|
10
|
+
|
11
|
+
gem 'activesupport'
|
12
|
+
require 'active_support/inflector'
|
13
|
+
require 'active_support/core_ext/hash/keys'
|
14
|
+
|
15
|
+
class Hash #:nodoc:
|
16
|
+
include ActiveSupport::CoreExtensions::Hash::Keys
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'yaml'
|
20
|
+
|
21
|
+
module Workling
|
22
|
+
class WorklingError < StandardError; end
|
23
|
+
class WorklingNotFoundError < WorklingError; end
|
24
|
+
class WorklingConnectionError < WorklingError; end
|
25
|
+
class QueueserverNotFoundError < WorklingError
|
26
|
+
def initialize
|
27
|
+
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)"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
class ConfigurationError < WorklingError
|
31
|
+
def initialize
|
32
|
+
super File.exist?(Workling.path('config', 'starling.yml')) ?
|
33
|
+
"config/starling.yml has been depracated. rename your config file to config/workling.yml then try again!" :
|
34
|
+
"config/workling.yml could not be loaded. check out README.markdown to see what this file should contain. "
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.path(*args)
|
39
|
+
if defined?(RAILS_ROOT)
|
40
|
+
File.join(RAILS_ROOT, *args)
|
41
|
+
else
|
42
|
+
File.join(Dir.pwd, *args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.env
|
47
|
+
@env ||= if defined?(RAILS_ENV)
|
48
|
+
RAILS_ENV.to_s
|
49
|
+
elsif defined?(RACK_ENV)
|
50
|
+
RACK_ENV.to_s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
mattr_accessor :load_path
|
55
|
+
@@load_path = [ File.expand_path(path('app', 'workers')) ]
|
56
|
+
|
57
|
+
VERSION = "0.4.9" unless defined?(VERSION)
|
58
|
+
|
59
|
+
#
|
60
|
+
# determine the client to use if nothing is specifically set. workling will try to detect
|
61
|
+
# starling, spawn, or bj, in that order. if none of these are found, notremoterunner will
|
62
|
+
# be used.
|
63
|
+
#
|
64
|
+
def self.select_default_client
|
65
|
+
if env == "test"
|
66
|
+
Workling::Clients::NotRemoteClient
|
67
|
+
elsif Workling::Clients::SpawnClient.installed?
|
68
|
+
Workling::Clients::SpawnClient
|
69
|
+
elsif Workling::Clients::BackgroundjobClient.installed?
|
70
|
+
Workling::Clients::BackgroundjobClient
|
71
|
+
else
|
72
|
+
Workling::Clients::NotRemoteClient
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.clients
|
77
|
+
{
|
78
|
+
'amqp' => Workling::Clients::AmqpClient,
|
79
|
+
'amqp_exchange' => Workling::Clients::AmqpExchangeClient,
|
80
|
+
'memcache' => Workling::Clients::MemcacheQueueClient,
|
81
|
+
'starling' => Workling::Clients::MemcacheQueueClient,
|
82
|
+
'memory_queue' => Workling::Clients::MemoryQueueClient,
|
83
|
+
'sqs' => Workling::Clients::SqsClient,
|
84
|
+
'xmpp' => Workling::Clients::XmppClient,
|
85
|
+
'backgroundjob' => Workling::Clients::BackgroundjobClient,
|
86
|
+
'not_remote' => Workling::Clients::NotRemoteClient,
|
87
|
+
'not' => Workling::Clients::NotClient,
|
88
|
+
'spawn' => Workling::Clients::SpawnClient,
|
89
|
+
'thread' => Workling::Clients::ThreadClient,
|
90
|
+
'rudeq' => Workling::Clients::RudeQClient
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.select_client
|
95
|
+
client_class = clients[Workling.config[:client]] || select_default_client
|
96
|
+
client_class.load
|
97
|
+
client_class
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# this will build the client to use for job dispatching and retrieval
|
102
|
+
# The look up does the following:
|
103
|
+
# 1. if Workling::Remote.client is set explicitly then that client is used
|
104
|
+
# 2. if workling.yml (or whatever file is used) contains a client section then that is used
|
105
|
+
# 3. otherwise the default client is built using the Workling.select_and_build_default_client method
|
106
|
+
#
|
107
|
+
def self.select_and_build_client
|
108
|
+
select_client.new
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# this will select the routing class
|
113
|
+
#
|
114
|
+
def self.select_and_build_routing
|
115
|
+
routing_class = {
|
116
|
+
'class_and_method' => Workling::Routing::ClassAndMethodRouting,
|
117
|
+
'static' => Workling::Routing::StaticRouting
|
118
|
+
}[Workling.config[:routing]] || Workling::Routing::ClassAndMethodRouting
|
119
|
+
routing_class.new
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# this will build the invoker which will run the daemon
|
124
|
+
#
|
125
|
+
def self.select_and_build_invoker
|
126
|
+
invoker_class = {
|
127
|
+
'basic_poller' => Workling::Invokers::BasicPoller,
|
128
|
+
'thread_pool_poller' => Workling::Invokers::ThreadPoolPoller,
|
129
|
+
'threaded_poller' => Workling::Invokers::ThreadedPoller,
|
130
|
+
|
131
|
+
'eventmachine_subscriber' => Workling::Invokers::EventmachineSubscriber,
|
132
|
+
'looped_subscriber' => Workling::Invokers::LoopedSubscriber,
|
133
|
+
'amqp_single_subscriber' => Workling::Invokers::AmqpSingleSubscriber,
|
134
|
+
}[Workling.config[:invoker]] || Workling::Invokers::BasicPoller
|
135
|
+
invoker_class.new(select_and_build_routing, select_client)
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# gets the worker instance, given a class. the optional method argument will cause an
|
140
|
+
# exception to be raised if the worker instance does not respoind to said method.
|
141
|
+
#
|
142
|
+
def self.find(clazz, method = nil)
|
143
|
+
begin
|
144
|
+
inst = clazz.to_s.camelize.constantize.new
|
145
|
+
rescue NameError
|
146
|
+
raise_not_found(clazz, method)
|
147
|
+
end
|
148
|
+
raise_not_found(clazz, method) if method && !inst.respond_to?(method)
|
149
|
+
inst
|
150
|
+
end
|
151
|
+
|
152
|
+
# returns Workling::Return::Store.instance.
|
153
|
+
def self.return
|
154
|
+
Workling::Return::Store.instance
|
155
|
+
end
|
156
|
+
|
157
|
+
#
|
158
|
+
# returns a config hash. reads ./config/workling.yml
|
159
|
+
#
|
160
|
+
mattr_writer :config
|
161
|
+
def self.config
|
162
|
+
return @@config if defined?(@@config) && @@config
|
163
|
+
|
164
|
+
return nil unless File.exists?(config_path)
|
165
|
+
|
166
|
+
@@config ||= YAML.load_file(config_path)[env || 'development'].symbolize_keys
|
167
|
+
@@config[:memcache_options].symbolize_keys! if @@config[:memcache_options]
|
168
|
+
@@config
|
169
|
+
end
|
170
|
+
|
171
|
+
mattr_writer :config_path
|
172
|
+
def self.config_path
|
173
|
+
return @@config_path if defined?(@@config_path) && @@config_path
|
174
|
+
@@config_path = File.join(RAILS_ROOT, 'config', 'workling.yml')
|
175
|
+
end
|
176
|
+
|
177
|
+
#
|
178
|
+
# Raises exceptions thrown inside of the worker. normally, these are logged to
|
179
|
+
# logger.error. it's easy to miss these log calls while developing, though.
|
180
|
+
#
|
181
|
+
mattr_writer :raise_exceptions
|
182
|
+
def raise_exceptions
|
183
|
+
return @@raise_exceptions if defined?(@@raise_exceptions)
|
184
|
+
@@raise_exceptions = (RAILS_ENV == "test" || RAILS_ENV == "development")
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.raise_exceptions?
|
188
|
+
@@raise_exceptions
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
def self.raise_not_found(clazz, method)
|
193
|
+
raise Workling::WorklingNotFoundError.new("could not find #{ clazz }:#{ method } workling. ")
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
require_in_tree "workling/discovery"
|
199
|
+
require_in_tree "workling/base"
|
200
|
+
require_in_tree "workling/remote"
|
201
|
+
|
202
|
+
require_in_tree "workling/clients/base"
|
203
|
+
require_in_tree "workling/clients/broker_base"
|
204
|
+
require_in_tree "workling/invokers/base"
|
205
|
+
require_in_tree "workling/return/store/base"
|
206
|
+
require_in_tree "workling/routing/base"
|
207
|
+
|
208
|
+
# load all possible extension classes
|
209
|
+
["clients", "invokers", "return/store", "routing"].each do |e_dirs|
|
210
|
+
Dir.glob(File.join(File.dirname(__FILE__), "workling", e_dirs, "*.rb")).each do |rb_file|
|
211
|
+
require File.join(File.dirname(rb_file), File.basename(rb_file, ".rb"))
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#
|
2
|
+
# All worker classes must inherit from this class, and be saved in app/workers.
|
3
|
+
#
|
4
|
+
# The Worker lifecycle:
|
5
|
+
# The Worker is loaded once, at which point the instance method 'create' is called.
|
6
|
+
#
|
7
|
+
# Invoking Workers:
|
8
|
+
# Calling async_my_method on the worker class will trigger background work.
|
9
|
+
# This means that the loaded Worker instance will receive a call to the method
|
10
|
+
# my_method(:uid => "thisjobsuid2348732947923").
|
11
|
+
#
|
12
|
+
# The Worker method must have a single hash argument. Note that the job :uid will
|
13
|
+
# be merged into the hash.
|
14
|
+
#
|
15
|
+
module Workling
|
16
|
+
class Base
|
17
|
+
|
18
|
+
cattr_writer :logger
|
19
|
+
def self.logger
|
20
|
+
@@logger ||= defined?(RAILS_DEFAULT_LOGGER) ? ::RAILS_DEFAULT_LOGGER : Logger.new($stdout)
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger
|
24
|
+
self.class.logger
|
25
|
+
end
|
26
|
+
|
27
|
+
cattr_accessor :exposed_methods
|
28
|
+
@@exposed_methods ||= {}
|
29
|
+
|
30
|
+
def self.inherited(subclass)
|
31
|
+
Workling::Discovery.discovered << subclass
|
32
|
+
end
|
33
|
+
|
34
|
+
# expose a method using a custom queue name
|
35
|
+
def self.expose(method, options)
|
36
|
+
self.exposed_methods[method.to_s] = options[:as]
|
37
|
+
end
|
38
|
+
|
39
|
+
# identify the queue for a given method
|
40
|
+
def self.queue_for(method)
|
41
|
+
if self.exposed_methods.has_key?(method)
|
42
|
+
self.exposed_methods[method]
|
43
|
+
else
|
44
|
+
"#{ self.to_s.tableize }/#{ method }".split("/").join("__") # Don't split with : because it messes up memcache stats
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# identify the method linked to the queue
|
49
|
+
def self.method_for(queue)
|
50
|
+
if self.exposed_methods.invert.has_key?(queue)
|
51
|
+
self.exposed_methods.invert[queue]
|
52
|
+
else
|
53
|
+
queue.split("__").last
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def method_for(queue)
|
58
|
+
self.class.method_for(queue)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def initialize
|
63
|
+
super
|
64
|
+
|
65
|
+
create
|
66
|
+
end
|
67
|
+
|
68
|
+
# Put worker initialization code in here. This is good for restarting jobs that
|
69
|
+
# were interrupted.
|
70
|
+
def create
|
71
|
+
end
|
72
|
+
|
73
|
+
# override this if you want to set up exception notification
|
74
|
+
def notify_exception(exception, method, options)
|
75
|
+
logger.error "WORKLING ERROR: runner could not invoke #{ self.class }:#{ method } with #{ options.inspect }. error was: #{ exception.inspect }\n #{ exception.backtrace.join("\n") }"
|
76
|
+
end
|
77
|
+
|
78
|
+
# takes care of suppressing remote errors but raising Workling::WorklingNotFoundError
|
79
|
+
# where appropriate. swallow workling exceptions so that everything behaves like remote code.
|
80
|
+
# otherwise StarlingRunner and SpawnRunner would behave too differently to NotRemoteRunner.
|
81
|
+
def dispatch_to_worker_method(method, options = {})
|
82
|
+
begin
|
83
|
+
options = default_options.merge(options)
|
84
|
+
self.send(method, options)
|
85
|
+
rescue Workling::WorklingError => e
|
86
|
+
raise e
|
87
|
+
rescue Exception => e
|
88
|
+
notify_exception e, method, options
|
89
|
+
|
90
|
+
# reraise after logging. the exception really can't go anywhere in many cases. (spawn traps the exception)
|
91
|
+
raise e if Workling.raise_exceptions?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# supply default_options as a hash in classes that inherit Workling::Base
|
96
|
+
def default_options
|
97
|
+
{}
|
98
|
+
end
|
99
|
+
|
100
|
+
# thanks to blaine cook for this suggestion.
|
101
|
+
def self.method_missing(method, *args, &block)
|
102
|
+
if method.to_s =~ /^asynch?_(.*)/
|
103
|
+
Workling::Remote.run(self.to_s.dasherize, $1, *args)
|
104
|
+
else
|
105
|
+
super
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|