parallel_minion 0.0.1 → 0.1.0
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.
- 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
|