parallel_minion 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +57 -13
- data/lib/parallel_minion/minion.rb +30 -27
- data/lib/parallel_minion/railtie.rb +1 -1
- data/lib/parallel_minion/version.rb +1 -1
- data/test/minion_scope_test.rb +3 -3
- data/test/minion_test.rb +5 -5
- data/test/test_db.sqlite3 +0 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba4bc73da8594cb544d75365ae616634584cb197
|
4
|
+
data.tar.gz: 14a7ccfde7406ba6f41b0c28b133fb989160c67f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9dca76b499fa4484575825bfe0e7a86feb49e3a46f921e1a34211c5db4bca6506ad3456a947b1423a8914fb0f371f29443bbd465d7db5525bae1c4b40f97ca6
|
7
|
+
data.tar.gz: 571076e7b09162becba4c8d1c8a79f3f568722b6f276798cbaf5f97cae8abff608c778ec6d0289e9ecc1c931a17bb8dc788f1170a7191e31377b2d989eb66b9e
|
data/README.md
CHANGED
@@ -1,49 +1,67 @@
|
|
1
1
|
parallel_minion
|
2
2
|
===============
|
3
3
|
|
4
|
-
Parallel Minion supports easily handing work off to
|
4
|
+
Parallel Minion supports easily handing work off to minions (threads) so that tasks
|
5
5
|
that would normally be performed sequentially can easily be executed in parallel.
|
6
6
|
This allows Ruby and Rails applications to very easily do many tasks at the same
|
7
7
|
time so that results are returned more quickly.
|
8
8
|
|
9
|
-
Our use-case for
|
9
|
+
Our use-case for minions is where an application grew to a point where it would
|
10
10
|
be useful to run some of the steps in fulfilling a single request in parallel.
|
11
11
|
|
12
12
|
## Features:
|
13
13
|
|
14
14
|
Exceptions
|
15
15
|
|
16
|
-
- Any exceptions raised in
|
16
|
+
- Any exceptions raised in minions are captured and propagated back to the
|
17
17
|
calling thread when #result is called
|
18
|
-
- Makes exception handling simple with a drop-in replacement
|
18
|
+
- Makes exception handling simple with a drop-in replacement for existing code
|
19
|
+
- Avoids having to implement more complex actors and supervisors required
|
20
|
+
by some concurrency frameworks
|
19
21
|
|
20
22
|
Timeouts
|
21
23
|
|
22
|
-
- Timeout when a
|
23
|
-
- Timeouts are a useful feature when one of the
|
24
|
+
- Timeout when a minion does not return within a specified time
|
25
|
+
- Timeouts are a useful feature when one of the minions fails to respond in a
|
24
26
|
reasonable amount of time. For example when a call to a remote service hangs
|
25
27
|
we can send back a partial response of other work that was completed rather
|
26
28
|
than just "hanging" or failing completely.
|
27
29
|
|
28
30
|
Logging
|
29
31
|
|
30
|
-
- Built-in support to log the duration of all
|
32
|
+
- Built-in support to log the duration of all minion tasks to make future analysis
|
31
33
|
of performance issues much easier
|
32
|
-
- Logs any exceptions thrown to assist
|
34
|
+
- Logs any exceptions thrown to assist with problem diagnosis
|
35
|
+
- Logging tags from the current thread are propagated to the minions thread
|
36
|
+
- The name of the thread in log entries is set to the description supplied for
|
37
|
+
the minion to make it easy to distinguish log entries by minion / thread
|
38
|
+
|
39
|
+
Rails Support
|
40
|
+
|
41
|
+
- When used in a Rails environment the current scope of specified models can be
|
42
|
+
propagated to the minions thread
|
33
43
|
|
34
44
|
## Example
|
35
45
|
|
36
46
|
Simple example
|
37
47
|
|
38
48
|
```ruby
|
39
|
-
ParallelMinion::Minion.new(10.days.ago, description: 'Doing something else in parallel', timeout: 1000) do |date|
|
49
|
+
minion = ParallelMinion::Minion.new(10.days.ago, description: 'Doing something else in parallel', timeout: 1000) do |date|
|
40
50
|
MyTable.where('created_at <= ?', date).count
|
41
51
|
end
|
52
|
+
|
53
|
+
# Do other work here...
|
54
|
+
|
55
|
+
# Retrieve the result of the minion
|
56
|
+
count = minion.result
|
57
|
+
|
58
|
+
puts "Found #{count} records"
|
42
59
|
```
|
43
60
|
|
44
61
|
## Example
|
45
62
|
|
46
|
-
For example, in the code below there are several steps that are performed
|
63
|
+
For example, in the code below there are several steps that are performed
|
64
|
+
sequentially and does not yet use minions:
|
47
65
|
|
48
66
|
```ruby
|
49
67
|
# Contrived example to show how to do parallel code execution
|
@@ -190,13 +208,39 @@ end
|
|
190
208
|
|
191
209
|
The above #process_request method should now take on average 1,810 milli-seconds
|
192
210
|
which is significantly faster than the 3,780 milli-seconds it took to perform
|
193
|
-
the exact same request
|
211
|
+
the exact same request prior to using minions.
|
194
212
|
|
195
|
-
The exact breakdown of which calls to do in the main thread versus a
|
213
|
+
The exact breakdown of which calls to do in the main thread versus a minion is determined
|
196
214
|
through experience and trial and error over time. The key is logging the duration
|
197
|
-
of each call which
|
215
|
+
of each call which minion does by default so that the exact processing breakdown
|
198
216
|
can be fine-tuned over time.
|
199
217
|
|
218
|
+
## Disabling Minions
|
219
|
+
|
220
|
+
In the event that strange problems are occurring in production and no one is
|
221
|
+
sure if it is due to running the minion tasks in parallel, it is simple to make
|
222
|
+
all minion tasks run in the calling thread.
|
223
|
+
|
224
|
+
It may also be useful to disable minions on a single production server to compare
|
225
|
+
its performance to that of the servers running with minions active.
|
226
|
+
|
227
|
+
To disable minions / make them run in the calling thread, add the following
|
228
|
+
lines to config/environments/production.rb:
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
# Make minions run immediately in the current thread
|
232
|
+
config.parallel_minion.enabled = false
|
233
|
+
```
|
234
|
+
|
235
|
+
## Notes:
|
236
|
+
|
237
|
+
- When using JRuby it is important to enable it's built-in thread-pooling by
|
238
|
+
adding the following line to .jrubyrc, or setting the appropriate command line option:
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
thread.pool.enabled=true
|
242
|
+
```
|
243
|
+
|
200
244
|
Meta
|
201
245
|
----
|
202
246
|
|
@@ -15,24 +15,23 @@ module ParallelMinion
|
|
15
15
|
# Give an infinite amount of time to wait for a Minion to complete a task
|
16
16
|
INFINITE = -1
|
17
17
|
|
18
|
-
# Sets whether to run in
|
18
|
+
# Sets whether minions are enabled to run in their own threads
|
19
19
|
#
|
20
|
-
# By Setting
|
21
|
-
# will run in the thread from which
|
22
|
-
# threads
|
20
|
+
# By Setting _enabled_ to false all Minions that have not yet been started
|
21
|
+
# will run in the thread from which it is created and not on its own thread
|
23
22
|
#
|
24
23
|
# This is useful:
|
25
24
|
# - to run tests under the Capybara gem
|
26
25
|
# - when debugging code so that all code is run sequentially in the current thread
|
27
26
|
#
|
28
|
-
# Note:
|
29
|
-
def self.
|
30
|
-
@@
|
27
|
+
# Note: Not recommended to set this setting to false in Production
|
28
|
+
def self.enabled=(enabled)
|
29
|
+
@@enabled = enabled
|
31
30
|
end
|
32
31
|
|
33
|
-
# Returns whether
|
34
|
-
def self.
|
35
|
-
@@
|
32
|
+
# Returns whether minions are enabled to run in their own threads
|
33
|
+
def self.enabled?
|
34
|
+
@@enabled
|
36
35
|
end
|
37
36
|
|
38
37
|
# The list of classes for which the current scope must be copied into the
|
@@ -66,14 +65,14 @@ module ParallelMinion
|
|
66
65
|
# - :timeout does not affect what happens to the Minion running the
|
67
66
|
# the task, it only affects how long #result will take to return.
|
68
67
|
# - The Minion will continue to run even after the timeout has been exceeded
|
69
|
-
# - If :
|
70
|
-
#
|
68
|
+
# - If :enabled is false, or ParallelMinion::Minion.enabled is false,
|
69
|
+
# then :timeout is ignored and assumed to be Minion::INFINITE
|
71
70
|
# since the code is run in the calling thread when the Minion is created
|
72
71
|
#
|
73
|
-
# :
|
74
|
-
# Whether the
|
72
|
+
# :enabled [Boolean]
|
73
|
+
# Whether the minion should run in a separate thread
|
75
74
|
# Not recommended in Production, but is useful for debugging purposes
|
76
|
-
# Default:
|
75
|
+
# Default: ParallelMinion::Minion.enabled?
|
77
76
|
#
|
78
77
|
# *args
|
79
78
|
# Any number of arguments can be supplied that are passed into the block
|
@@ -112,20 +111,24 @@ module ParallelMinion
|
|
112
111
|
|
113
112
|
options = self.class.extract_options!(args).dup
|
114
113
|
|
115
|
-
@timeout
|
114
|
+
@timeout = (options.delete(:timeout) || Minion::INFINITE).to_f
|
116
115
|
@description = (options.delete(:description) || 'Minion').to_s
|
117
116
|
@log_exception = options.delete(:log_exception)
|
118
|
-
@
|
117
|
+
@enabled = options.delete(:enabled)
|
118
|
+
@enabled = self.class.enabled? if @enabled.nil?
|
119
119
|
|
120
120
|
# Warn about any unknown options.
|
121
|
-
options.each_pair
|
121
|
+
options.each_pair do |key,val|
|
122
|
+
logger.warn "Ignoring unknown option: #{key.inspect} => #{val.inspect}"
|
123
|
+
warn "ParallelMinion::Minion Ignoring unknown option: #{key.inspect} => #{val.inspect}"
|
124
|
+
end
|
122
125
|
|
123
126
|
# Run the supplied block of code in the current thread for testing or
|
124
127
|
# debugging purposes
|
125
|
-
if @
|
128
|
+
if @enabled == false
|
126
129
|
begin
|
127
|
-
logger.info("Started
|
128
|
-
logger.benchmark_info("Completed
|
130
|
+
logger.info("Started in the current thread: #{@description}")
|
131
|
+
logger.benchmark_info("Completed in the current thread: #{@description}", log_exception: @log_exception) do
|
129
132
|
@result = instance_exec(*args, &block)
|
130
133
|
end
|
131
134
|
rescue Exception => exc
|
@@ -189,12 +192,12 @@ module ParallelMinion
|
|
189
192
|
|
190
193
|
# Returns [Boolean] whether the minion is still working on the assigned task
|
191
194
|
def working?
|
192
|
-
|
195
|
+
enabled? ? @thread.alive? : false
|
193
196
|
end
|
194
197
|
|
195
198
|
# Returns [Boolean] whether the minion has completed working on the task
|
196
199
|
def completed?
|
197
|
-
|
200
|
+
enabled? ? @thread.stop? : true
|
198
201
|
end
|
199
202
|
|
200
203
|
# Returns [Boolean] whether the minion failed while performing the assigned task
|
@@ -211,9 +214,9 @@ module ParallelMinion
|
|
211
214
|
duration <= 0 ? 0 : duration
|
212
215
|
end
|
213
216
|
|
214
|
-
# Returns [Boolean] whether
|
215
|
-
def
|
216
|
-
@
|
217
|
+
# Returns [Boolean] whether this minion is enabled to run in a separate thread
|
218
|
+
def enabled?
|
219
|
+
@enabled
|
217
220
|
end
|
218
221
|
|
219
222
|
# Returns the current scopes for each of the models for which scopes will be
|
@@ -225,7 +228,7 @@ module ParallelMinion
|
|
225
228
|
|
226
229
|
protected
|
227
230
|
|
228
|
-
@@
|
231
|
+
@@enabled = true
|
229
232
|
@@scoped_classes = []
|
230
233
|
|
231
234
|
# Extract options from a hash.
|
@@ -9,7 +9,7 @@ module ParallelMinion #:nodoc:
|
|
9
9
|
# Rails::Application.configure do
|
10
10
|
#
|
11
11
|
# # Run Minions in the current thread to make debugging easier
|
12
|
-
# config.parallel_minion.
|
12
|
+
# config.parallel_minion.enabled = false
|
13
13
|
#
|
14
14
|
# # Add a model so that its current scope is copied to the Minion
|
15
15
|
# config.parallel_minion.scoped_classes << MyScopedModel
|
data/test/minion_scope_test.rb
CHANGED
@@ -34,8 +34,8 @@ end
|
|
34
34
|
class MinionScopeTest < Test::Unit::TestCase
|
35
35
|
|
36
36
|
context ParallelMinion::Minion do
|
37
|
-
[false, true].each do |
|
38
|
-
context ".new with
|
37
|
+
[false, true].each do |enabled|
|
38
|
+
context ".new with enabled: #{enabled.inspect}" do
|
39
39
|
setup do
|
40
40
|
Person.create(name: 'Jack', state: 'FL', zip_code: 38729)
|
41
41
|
Person.create(name: 'John', state: 'FL', zip_code: 35363)
|
@@ -45,7 +45,7 @@ class MinionScopeTest < Test::Unit::TestCase
|
|
45
45
|
Person.create(name: 'James', state: 'CA', zip_code: 123123)
|
46
46
|
# Instruct Minions to adhere to any dynamic scopes for Person model
|
47
47
|
ParallelMinion::Minion.scoped_classes << Person
|
48
|
-
ParallelMinion::Minion.
|
48
|
+
ParallelMinion::Minion.enabled = enabled
|
49
49
|
end
|
50
50
|
|
51
51
|
teardown do
|
data/test/minion_test.rb
CHANGED
@@ -16,10 +16,10 @@ class MinionTest < Test::Unit::TestCase
|
|
16
16
|
|
17
17
|
context ParallelMinion::Minion do
|
18
18
|
|
19
|
-
[false, true].each do |
|
20
|
-
context ".new with
|
19
|
+
[false, true].each do |enabled|
|
20
|
+
context ".new with enabled: #{enabled.inspect}" do
|
21
21
|
setup do
|
22
|
-
ParallelMinion::Minion.
|
22
|
+
ParallelMinion::Minion.enabled = enabled
|
23
23
|
end
|
24
24
|
|
25
25
|
should 'without parameters' do
|
@@ -96,8 +96,8 @@ class MinionTest < Test::Unit::TestCase
|
|
96
96
|
|
97
97
|
should 'timeout' do
|
98
98
|
minion = ParallelMinion::Minion.new(description: 'Test', timeout: 100) { sleep 1 }
|
99
|
-
# Only Parallel Minions time-out when they exceed timeout
|
100
|
-
|
99
|
+
# Only Parallel Minions time-out when they exceed the timeout
|
100
|
+
if enabled
|
101
101
|
assert_equal nil, minion.result
|
102
102
|
end
|
103
103
|
end
|
data/test/test_db.sqlite3
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parallel_minion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Reid Morrison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: semantic_logger
|