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.
- data/CHANGES.markdown +10 -0
- data/README.markdown +161 -4
- data/bin/workling_client +28 -0
- metadata +3 -75
- data/lib/rude_q/client.rb +0 -11
- data/lib/workling.rb +0 -150
- data/lib/workling/base.rb +0 -71
- data/lib/workling/clients/amqp_client.rb +0 -56
- data/lib/workling/clients/base.rb +0 -57
- data/lib/workling/clients/memcache_queue_client.rb +0 -83
- data/lib/workling/discovery.rb +0 -14
- data/lib/workling/remote.rb +0 -42
- data/lib/workling/remote/invokers/base.rb +0 -124
- data/lib/workling/remote/invokers/basic_poller.rb +0 -41
- data/lib/workling/remote/invokers/eventmachine_subscriber.rb +0 -41
- data/lib/workling/remote/invokers/threaded_poller.rb +0 -140
- data/lib/workling/remote/runners/backgroundjob_runner.rb +0 -35
- data/lib/workling/remote/runners/base.rb +0 -42
- data/lib/workling/remote/runners/client_runner.rb +0 -45
- data/lib/workling/remote/runners/not_remote_runner.rb +0 -23
- data/lib/workling/remote/runners/rudeq_runner.rb +0 -23
- data/lib/workling/remote/runners/spawn_runner.rb +0 -38
- data/lib/workling/remote/runners/starling_runner.rb +0 -13
- data/lib/workling/return/store/base.rb +0 -42
- data/lib/workling/return/store/iterator.rb +0 -24
- data/lib/workling/return/store/memory_return_store.rb +0 -26
- data/lib/workling/return/store/rudeq_return_store.rb +0 -24
- data/lib/workling/return/store/starling_return_store.rb +0 -31
- data/lib/workling/routing/base.rb +0 -13
- data/lib/workling/routing/class_and_method_routing.rb +0 -55
- data/lib/workling/rudeq.rb +0 -7
- data/lib/workling/rudeq/client.rb +0 -17
- data/lib/workling/rudeq/poller.rb +0 -116
- data/test/class_and_method_routing_test.rb +0 -18
- data/test/clients/memory_queue_client.rb +0 -36
- data/test/discovery_test.rb +0 -13
- data/test/invoker_basic_poller_test.rb +0 -29
- data/test/invoker_eventmachine_subscription_test.rb +0 -26
- data/test/invoker_threaded_poller_test.rb +0 -34
- data/test/memcachequeue_client_test.rb +0 -36
- data/test/memory_return_store_test.rb +0 -32
- data/test/mocks/client.rb +0 -9
- data/test/mocks/logger.rb +0 -5
- data/test/mocks/rude_queue.rb +0 -9
- data/test/mocks/spawn.rb +0 -5
- data/test/not_remote_runner_test.rb +0 -11
- data/test/remote_runner_test.rb +0 -58
- data/test/rescue_test.rb +0 -24
- data/test/return_store_test.rb +0 -24
- data/test/rudeq_client_test.rb +0 -30
- data/test/rudeq_poller_test.rb +0 -14
- data/test/rudeq_return_store_test.rb +0 -20
- data/test/rudeq_runner_test.rb +0 -22
- data/test/runners/thread_runner.rb +0 -22
- data/test/spawn_runner_test.rb +0 -10
- data/test/starling_return_store_test.rb +0 -29
- data/test/starling_runner_test.rb +0 -8
- data/test/test_helper.rb +0 -50
- data/test/workers/analytics/invites.rb +0 -10
- data/test/workers/util.rb +0 -25
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
require 'workling/remote/invokers/base'
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
-
# A basic polling invoker.
|
|
5
|
-
#
|
|
6
|
-
module Workling
|
|
7
|
-
module Remote
|
|
8
|
-
module Invokers
|
|
9
|
-
class BasicPoller < Workling::Remote::Invokers::Base
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
# set up client, sleep time
|
|
13
|
-
#
|
|
14
|
-
def initialize(routing, client_class)
|
|
15
|
-
super
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
#
|
|
19
|
-
# Starts main Invoker Loop. The invoker runs until stop() is called.
|
|
20
|
-
#
|
|
21
|
-
def listen
|
|
22
|
-
connect do
|
|
23
|
-
loop_routes do |route|
|
|
24
|
-
if args = @client.retrieve(route)
|
|
25
|
-
run(route, args)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
#
|
|
32
|
-
# Gracefully stops the Invoker. The currently executing Jobs should be allowed
|
|
33
|
-
# to finish.
|
|
34
|
-
#
|
|
35
|
-
def stop
|
|
36
|
-
@shutdown = true
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
require 'eventmachine'
|
|
2
|
-
require 'workling/remote/invokers/base'
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
-
# Subscribes the workers to the correct queues.
|
|
6
|
-
#
|
|
7
|
-
module Workling
|
|
8
|
-
module Remote
|
|
9
|
-
module Invokers
|
|
10
|
-
class EventmachineSubscriber < Workling::Remote::Invokers::Base
|
|
11
|
-
|
|
12
|
-
def initialize(routing, client_class)
|
|
13
|
-
super
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
#
|
|
17
|
-
# Starts EM loop and sets up subscription callbacks for workers.
|
|
18
|
-
#
|
|
19
|
-
def listen
|
|
20
|
-
EM.run do
|
|
21
|
-
connect do
|
|
22
|
-
routes.each do |route|
|
|
23
|
-
@client.subscribe(route) do |args|
|
|
24
|
-
begin
|
|
25
|
-
run(route, args)
|
|
26
|
-
rescue
|
|
27
|
-
logger.error("EventmachineSubscriber listen error on #{route}: #{$!}")
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def stop
|
|
36
|
-
EM.stop if EM.reactor_running?
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
require 'workling/remote/invokers/base'
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
-
# A threaded polling Invoker.
|
|
5
|
-
#
|
|
6
|
-
# TODO: refactor this to make use of the base class.
|
|
7
|
-
#
|
|
8
|
-
module Workling
|
|
9
|
-
module Remote
|
|
10
|
-
module Invokers
|
|
11
|
-
class ThreadedPoller < Workling::Remote::Invokers::Base
|
|
12
|
-
|
|
13
|
-
cattr_accessor :sleep_time, :reset_time
|
|
14
|
-
|
|
15
|
-
def initialize(routing, client_class)
|
|
16
|
-
super
|
|
17
|
-
|
|
18
|
-
ThreadedPoller.sleep_time = Workling.config[:sleep_time] || 2
|
|
19
|
-
ThreadedPoller.reset_time = Workling.config[:reset_time] || 30
|
|
20
|
-
|
|
21
|
-
@workers = ThreadGroup.new
|
|
22
|
-
@mutex = Mutex.new
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def listen
|
|
26
|
-
# Create a thread for each worker.
|
|
27
|
-
Workling::Discovery.discovered.each do |clazz|
|
|
28
|
-
logger.debug("Discovered listener #{clazz}")
|
|
29
|
-
@workers.add(Thread.new(clazz) { |c| clazz_listen(c) })
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Wait for all workers to complete
|
|
33
|
-
@workers.list.each { |t| t.join }
|
|
34
|
-
|
|
35
|
-
logger.debug("Reaped listener threads. ")
|
|
36
|
-
|
|
37
|
-
# Clean up all the connections.
|
|
38
|
-
ActiveRecord::Base.verify_active_connections!
|
|
39
|
-
logger.debug("Cleaned up connection: out!")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Check if all Worker threads have been started.
|
|
43
|
-
def started?
|
|
44
|
-
logger.debug("checking if started... list size is #{ worker_threads }")
|
|
45
|
-
Workling::Discovery.discovered.size == worker_threads
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# number of worker threads running
|
|
49
|
-
def worker_threads
|
|
50
|
-
@workers.list.size
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Gracefully stop processing
|
|
54
|
-
def stop
|
|
55
|
-
logger.info("stopping threaded poller...")
|
|
56
|
-
sleep 1 until started? # give it a chance to start up before shutting down.
|
|
57
|
-
logger.info("Giving Listener Threads a chance to shut down. This may take a while... ")
|
|
58
|
-
@workers.list.each { |w| w[:shutdown] = true }
|
|
59
|
-
logger.info("Listener threads were shut down. ")
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Listen for one worker class
|
|
63
|
-
def clazz_listen(clazz)
|
|
64
|
-
logger.debug("Listener thread #{clazz.name} started")
|
|
65
|
-
|
|
66
|
-
# Read thread configuration if available
|
|
67
|
-
if Workling.config.has_key?(:listeners)
|
|
68
|
-
if Workling.config[:listeners].has_key?(clazz.to_s)
|
|
69
|
-
config = Workling.config[:listeners][clazz.to_s].symbolize_keys
|
|
70
|
-
thread_sleep_time = config[:sleep_time] if config.has_key?(:sleep_time)
|
|
71
|
-
Thread.current.priority = config[:priority] if config.has_key?(:priority)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
thread_sleep_time ||= self.class.sleep_time
|
|
76
|
-
|
|
77
|
-
# Setup connection to client (one per thread)
|
|
78
|
-
connection = @client_class.new
|
|
79
|
-
connection.connect
|
|
80
|
-
logger.info("** Starting client #{ connection.class } for #{clazz.name} queue")
|
|
81
|
-
|
|
82
|
-
# Start dispatching those messages
|
|
83
|
-
while (!Thread.current[:shutdown]) do
|
|
84
|
-
begin
|
|
85
|
-
|
|
86
|
-
# Wrap code calling ActiveRecord::Base.connection_active? in a
|
|
87
|
-
# mutex to avoid problem with spawning threads on multi-core
|
|
88
|
-
# machines running MySQL.
|
|
89
|
-
@mutex.synchronize do
|
|
90
|
-
unless ActiveRecord::Base.connection.active? # Keep MySQL connection alive
|
|
91
|
-
unless ActiveRecord::Base.connection.reconnect!
|
|
92
|
-
logger.fatal("Failed - Database not available!")
|
|
93
|
-
break
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Dispatch and process the messages
|
|
99
|
-
n = dispatch!(connection, clazz)
|
|
100
|
-
logger.debug("Listener thread #{clazz.name} processed #{n.to_s} queue items") if n > 0
|
|
101
|
-
sleep(thread_sleep_time) unless n > 0
|
|
102
|
-
|
|
103
|
-
# If there is a memcache error, hang for a bit to give it a chance to fire up again
|
|
104
|
-
# and reset the connection.
|
|
105
|
-
rescue Workling::WorklingConnectionError
|
|
106
|
-
logger.warn("Listener thread #{clazz.name} failed to connect. Resetting connection.")
|
|
107
|
-
sleep(self.class.reset_time)
|
|
108
|
-
connection.reset
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
logger.debug("Listener thread #{clazz.name} ended")
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Dispatcher for one worker class. Will throw MemCacheError if unable to connect.
|
|
116
|
-
# Returns the number of worker methods called
|
|
117
|
-
def dispatch!(connection, clazz)
|
|
118
|
-
n = 0
|
|
119
|
-
for queue in @routing.queue_names_routing_class(clazz)
|
|
120
|
-
begin
|
|
121
|
-
result = connection.retrieve(queue)
|
|
122
|
-
if result
|
|
123
|
-
n += 1
|
|
124
|
-
handler = @routing[queue]
|
|
125
|
-
method_name = @routing.method_name(queue)
|
|
126
|
-
logger.debug("Calling #{handler.class.to_s}\##{method_name}(#{result.inspect})")
|
|
127
|
-
handler.dispatch_to_worker_method(method_name, result)
|
|
128
|
-
end
|
|
129
|
-
rescue MemCache::MemCacheError => e
|
|
130
|
-
logger.error("FAILED to connect with queue #{ queue }: #{ e } }")
|
|
131
|
-
raise e
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
return n
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
require 'workling/remote/runners/base'
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
-
# Use Ara Howards BackgroundJob to run the work. BackgroundJob loads Rails once per requested Job.
|
|
5
|
-
# It persists over the database, and there is no requirement for separate processes to be started.
|
|
6
|
-
# Since rails has to load before each request, it takes a moment for the job to run.
|
|
7
|
-
#
|
|
8
|
-
module Workling
|
|
9
|
-
module Remote
|
|
10
|
-
module Runners
|
|
11
|
-
class BackgroundjobRunner < Workling::Remote::Runners::Base
|
|
12
|
-
cattr_accessor :routing
|
|
13
|
-
|
|
14
|
-
def initialize
|
|
15
|
-
BackgroundjobRunner.routing =
|
|
16
|
-
Workling::Routing::ClassAndMethodRouting.new
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# passes the job to bj by serializing the options to xml and passing them to
|
|
20
|
-
# ./script/bj_invoker.rb, which in turn routes the deserialized args to the
|
|
21
|
-
# appropriate worker.
|
|
22
|
-
def run(clazz, method, options = {})
|
|
23
|
-
stdin = @@routing.queue_for(clazz, method) +
|
|
24
|
-
" " +
|
|
25
|
-
options.to_xml(:indent => 0, :skip_instruct => true)
|
|
26
|
-
|
|
27
|
-
Bj.submit "./script/runner ./script/bj_invoker.rb",
|
|
28
|
-
:stdin => stdin
|
|
29
|
-
|
|
30
|
-
return nil # that means nothing!
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Base class for Workling Runners.
|
|
3
|
-
#
|
|
4
|
-
# Runners must subclass this and implement the method
|
|
5
|
-
#
|
|
6
|
-
# Workling::Remote::Runners::Base#run(clazz, method, options = {})
|
|
7
|
-
#
|
|
8
|
-
# which is responsible for pushing the requested job into the background. Depending
|
|
9
|
-
# on the Runner, this may require other code to dequeue the job. The actual
|
|
10
|
-
# invocation of the runner should be done like this:
|
|
11
|
-
#
|
|
12
|
-
# Workling.find(clazz, method).dispatch_to_worker_method(method, options)
|
|
13
|
-
#
|
|
14
|
-
# This ensures for consistent logging and handling of propagated exceptions. You can
|
|
15
|
-
# also call the convenience method
|
|
16
|
-
#
|
|
17
|
-
# Workling::Remote::Runners::Base#dispatch!(clazz, method, options)
|
|
18
|
-
#
|
|
19
|
-
# which invokes this for you.
|
|
20
|
-
#
|
|
21
|
-
module Workling
|
|
22
|
-
module Remote
|
|
23
|
-
module Runners
|
|
24
|
-
class Base
|
|
25
|
-
|
|
26
|
-
# runner uses this to connect to a job broker
|
|
27
|
-
cattr_accessor :client
|
|
28
|
-
|
|
29
|
-
# default logger defined in Workling::Base.logger
|
|
30
|
-
def logger
|
|
31
|
-
Workling::Base.logger
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# find the worker instance and invoke it. Invoking the worker method like this ensures for
|
|
35
|
-
# consistent logging and handling of propagated exceptions.
|
|
36
|
-
def dispatch!(clazz, method, options)
|
|
37
|
-
Workling.find(clazz, method).dispatch_to_worker_method(method, options)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
require 'workling/remote/runners/base'
|
|
2
|
-
require 'workling/clients/memcache_queue_client'
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
-
# Runs Jobs over a Client. The client should be a subclass of Workling::Client::Base.
|
|
6
|
-
# Set the client like this:
|
|
7
|
-
#
|
|
8
|
-
# Workling::Remote::Runners::ClientRunner.client = Workling::Clients::AmqpClient.new
|
|
9
|
-
#
|
|
10
|
-
# Jobs are dispatched by requesting them on the Client. The Runner takes care of mapping of queue names to worker code.
|
|
11
|
-
# this is done with Workling::ClassAndMethodRouting, but you can use your own by sublassing Workling::Routing.
|
|
12
|
-
# Don’t worry about any of this if you’re not dealing directly with the queues.
|
|
13
|
-
#
|
|
14
|
-
# There’s a workling-client daemon that uses the configured invoker to retrieve work and dispatching these to the
|
|
15
|
-
# responsible workers. If you intend to run this on a remote machine, then just check out your rails project
|
|
16
|
-
# there and start up the workling client like this: ruby script/workling_client run.
|
|
17
|
-
#
|
|
18
|
-
module Workling
|
|
19
|
-
module Remote
|
|
20
|
-
module Runners
|
|
21
|
-
class ClientRunner < Workling::Remote::Runners::Base
|
|
22
|
-
|
|
23
|
-
# Routing class. Workling::Routing::ClassAndMethodRouting.new by default.
|
|
24
|
-
cattr_accessor :routing
|
|
25
|
-
@@routing ||= Workling::Routing::ClassAndMethodRouting.new
|
|
26
|
-
|
|
27
|
-
# The workling Client class. Workling::Clients::MemcacheQueueClient.new by default.
|
|
28
|
-
cattr_accessor :client
|
|
29
|
-
@@client ||= Workling::Clients::MemcacheQueueClient.new
|
|
30
|
-
|
|
31
|
-
# enqueues the job onto the client
|
|
32
|
-
def run(clazz, method, options = {})
|
|
33
|
-
|
|
34
|
-
# neet to connect in here as opposed to the constructor, since the EM loop is
|
|
35
|
-
# not available there.
|
|
36
|
-
@connected ||= self.class.client.connect
|
|
37
|
-
|
|
38
|
-
self.class.client.request(@@routing.queue_for(clazz, method), options)
|
|
39
|
-
|
|
40
|
-
return nil
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
require 'workling/remote/runners/base'
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
-
# directly dispatches to the worker method, in-process. options are first marshalled then dumped
|
|
5
|
-
# in order to simulate the sideeffects of a remote call.
|
|
6
|
-
#
|
|
7
|
-
module Workling
|
|
8
|
-
module Remote
|
|
9
|
-
module Runners
|
|
10
|
-
class NotRemoteRunner < Workling::Remote::Runners::Base
|
|
11
|
-
|
|
12
|
-
# directly dispatches to the worker method, in-process. options are first marshalled then dumped
|
|
13
|
-
# in order to simulate the sideeffects of a remote call.
|
|
14
|
-
def run(clazz, method, options = {})
|
|
15
|
-
options = Marshal.load(Marshal.dump(options)) # get this to behave more like the remote runners
|
|
16
|
-
dispatch!(clazz, method, options)
|
|
17
|
-
|
|
18
|
-
return nil # nada. niente.
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
require 'workling/remote/runners/base'
|
|
2
|
-
|
|
3
|
-
module Workling
|
|
4
|
-
module Remote
|
|
5
|
-
module Runners
|
|
6
|
-
class RudeqRunner < Workling::Remote::Runners::Base
|
|
7
|
-
cattr_accessor :routing
|
|
8
|
-
cattr_accessor :client
|
|
9
|
-
|
|
10
|
-
def initialize
|
|
11
|
-
RudeqRunner.client = Workling::Rudeq::Client.new
|
|
12
|
-
RudeqRunner.routing = Workling::Starling::Routing::ClassAndMethodRouting.new
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def run(clazz, method, options = {})
|
|
16
|
-
RudeqRunner.client.set(@@routing.queue_for(clazz, method), options)
|
|
17
|
-
|
|
18
|
-
return nil # empty.
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
require 'workling/remote/runners/base'
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
-
# Run the job over the spawn plugin. Refer to the README for instructions on
|
|
5
|
-
# installing Spawn.
|
|
6
|
-
#
|
|
7
|
-
# Spawn forks the entire process once for each job. This means that the job starts
|
|
8
|
-
# with a very low latency, but takes up more memory for each job.
|
|
9
|
-
#
|
|
10
|
-
# It's also possible to configure Spawn to start a Thread for each job. Do this
|
|
11
|
-
# by setting
|
|
12
|
-
#
|
|
13
|
-
# Workling::Remote::Runners::SpawnRunner.options = { :method => :thread }
|
|
14
|
-
#
|
|
15
|
-
# Have a look at the Spawn README to find out more about the characteristics of this.
|
|
16
|
-
#
|
|
17
|
-
module Workling
|
|
18
|
-
module Remote
|
|
19
|
-
module Runners
|
|
20
|
-
class SpawnRunner < Workling::Remote::Runners::Base
|
|
21
|
-
cattr_accessor :options
|
|
22
|
-
|
|
23
|
-
# use thread for development and test modes. easier to hunt down exceptions that way.
|
|
24
|
-
@@options = { :method => (RAILS_ENV == "test" || RAILS_ENV == "development" ? :thread : :fork) }
|
|
25
|
-
include Spawn if Workling.spawn_installed?
|
|
26
|
-
|
|
27
|
-
# dispatches to Spawn, using the :fork option.
|
|
28
|
-
def run(clazz, method, options = {})
|
|
29
|
-
spawn(SpawnRunner.options) do # exceptions are trapped in here.
|
|
30
|
-
dispatch!(clazz, method, options)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
return nil # that means nothing!
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|