jruby_threach 0.3.0-java
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +89 -0
- data/Rakefile +43 -0
- data/lib/jruby_threach.rb +300 -0
- data/samples.rb +60 -0
- data/spec/jruby_threach_spec.rb +7 -0
- data/spec/spec_helper.rb +12 -0
- metadata +123 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", "~> 2.3.0"
|
10
|
+
gem "yard", "~> 0.6.0"
|
11
|
+
gem "bundler", "~> 1.0.0"
|
12
|
+
gem "jeweler", "~> 1.6.0"
|
13
|
+
gem "rcov", ">= 0"
|
14
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 BillDueber
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# JRuby_threach
|
2
|
+
|
3
|
+
`jruby_threach` adds a method to Enumerable called `#threach` that takes a block and distributes the objects produced by a call to `#each` (or the enumerator of your choice) automatically to a set of consumers.
|
4
|
+
|
5
|
+
`jruby_threach` is roughly seven zillion times better than it's vanilla MRI counterpart (`threach`) in that it can handle non-local exits out of the block (`break` or `raise`) without incident. This is possible due to the more powerful queue class available in Java.
|
6
|
+
|
7
|
+
## What it does
|
8
|
+
|
9
|
+
`jruby_threach` effectively creates a classic producer/consumer setup with a size-restricted queue between them. A single producer (running in the main thread) runs the given iterator (`#each` by default) and stuffs values into the queue. The consumers then grab these values and process them.
|
10
|
+
|
11
|
+
A few things can be gleaned from this:
|
12
|
+
|
13
|
+
* If your consumer code is faster than your producer code, your consumers are going to be starved
|
14
|
+
* You should allocate no more consumers than the speed multiplier between producer and consumer code (e.g., if your producer code -- the call to #each -- is only twice as fast as the block you're passing, don't bother to allocate more than two consumer threads).
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
`jruby_threach` is on rubygems.org, so you should just be able to do
|
19
|
+
|
20
|
+
gem install jruby_threach
|
21
|
+
# or JRuby --1.9 -S gem install JRuby_threach for 1.9
|
22
|
+
|
23
|
+
## Use
|
24
|
+
|
25
|
+
`jruby_threach` works under JRuby with and without the --1.9 switch.
|
26
|
+
|
27
|
+
require 'jruby_threach'
|
28
|
+
|
29
|
+
# Process an array of data with two threads (default is to call #each)
|
30
|
+
ary.threach(2) {|i| process_data(i)}
|
31
|
+
|
32
|
+
# Specify the iterator at will
|
33
|
+
ary.threach(2, :each_with_index) {|val, i| process_index_value(i, val)}
|
34
|
+
|
35
|
+
# #threach doesn't care what the arity of your enumerator is, so long
|
36
|
+
# as the block you pass matches it
|
37
|
+
hash.threach(3) {|k,v| process_key_value(k,v)}
|
38
|
+
|
39
|
+
# Anything that mixes in enumerable is game. e.g., process lines of a file
|
40
|
+
File.open('myfile.txt').threach(3, :each_line) {|line| process_line(line)}
|
41
|
+
|
42
|
+
# Pass in a thread count of 0 to just call the underlying method
|
43
|
+
ary.threach(0)
|
44
|
+
|
45
|
+
|
46
|
+
## mthreach -- thread the producers as well
|
47
|
+
|
48
|
+
There are a couple obvious use cases (e.g., work on a whole list of files) where it might be useful to thread the producer as well. `mthreach` operates on an array of enumerables, using multiple threads to use them to fill a queue simultaneously, and then uses the normal `threach` logic to have multiple consumers pulling from that queue to do work.
|
49
|
+
|
50
|
+
# Use 2 producer threads and 3 consumer threads to process lines
|
51
|
+
Dir.glob('*.txt').map{|f| File.open(f)}.mthreach(2,3,:each_line) {|l|...}
|
52
|
+
|
53
|
+
This starts to make more sense if your producer objects aren't super-speedy (e.g., an object that's crawling a list of URLs off the web, hitting a database or set of databases, etc.) or if you have the exemplar case of multiple files (e.g., a set of CSV files where you're going to process each line and stick it into a database).
|
54
|
+
|
55
|
+
|
56
|
+
## Threach::MultiEnum
|
57
|
+
|
58
|
+
You can also use Threach::MultiEnum, the class behind `#mthreach`, by itself, for whenever you have a set of iterators and want to deal with them as if they were a single thing.
|
59
|
+
|
60
|
+
me = Threach::MultiEnum.new(
|
61
|
+
[1..10, 'a'..'z', File.open('myfile.txt')], # array of enumerables
|
62
|
+
:each, # enumerator method to use
|
63
|
+
10, # size of the internal queue
|
64
|
+
3) # number of threads. nil => one per enum
|
65
|
+
me.each {|item| process_item(item)}
|
66
|
+
|
67
|
+
|
68
|
+
# Things to know
|
69
|
+
|
70
|
+
**You're using threads!** Your code needs to be thread-safe (esp. make sure your database connections can cope), and of course we can't guarantee what order the data will be processed in.
|
71
|
+
|
72
|
+
**Spurious warning** As of this writing, breaking out of a `threach` loop (by calling `break` in the passed block) causes JRuby to print a warning of the form, 'Exception in thread "RubyThread-44: samples.rb:1"org.JRuby.exceptions.JumpException$BreakJump'. Hopefully this can be tracked down and eliminated by the JRuby team.
|
73
|
+
|
74
|
+
|
75
|
+
## Contributing to JRuby_threach
|
76
|
+
|
77
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
78
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
79
|
+
* Fork the project
|
80
|
+
* Start a feature/bugfix branch
|
81
|
+
* Commit and push until you are happy with your contribution
|
82
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
83
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
84
|
+
|
85
|
+
## Copyright
|
86
|
+
|
87
|
+
Copyright (c) 2011 BillDueber. See LICENSE.txt for
|
88
|
+
further details.
|
89
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "jruby_threach"
|
18
|
+
gem.homepage = "http://github.com/billdueber/jruby_threach"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Very simply run a block of code using multiple threads under jruby}
|
21
|
+
gem.description = %Q{Run a block of code in multiple threads. Similar to threach, but with the ability to deal with breaks/exceptions (MRI can't because of unreliable timeouts).}
|
22
|
+
gem.email = "bill@dueber.com"
|
23
|
+
gem.authors = ["BillDueber"]
|
24
|
+
gem.platform = 'java'
|
25
|
+
# dependencies defined in Gemfile
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'yard'
|
43
|
+
YARD::Rake::YardocTask.new
|
@@ -0,0 +1,300 @@
|
|
1
|
+
require 'java'
|
2
|
+
java_import java.util.concurrent.ArrayBlockingQueue
|
3
|
+
java_import java.util.concurrent.TimeUnit
|
4
|
+
java_import org.jruby.exceptions.JumpException::BreakJump
|
5
|
+
|
6
|
+
module Threach
|
7
|
+
|
8
|
+
DEBUG = false
|
9
|
+
|
10
|
+
# Exception for when a consumer encounters a 'break'
|
11
|
+
class ThreachBreak < RuntimeError; end
|
12
|
+
# Exception to note that another thread has told the rest of us
|
13
|
+
# to wind it down due to an error
|
14
|
+
class ThreachNotMyError < RuntimeError; end
|
15
|
+
# Exception that indicates that we've legitimately run out of data
|
16
|
+
# to process
|
17
|
+
class ThreachEndOfRun < RuntimeError; end
|
18
|
+
|
19
|
+
|
20
|
+
# An ArrayBlockingQueue with reasonable defaults
|
21
|
+
# for timeouts.
|
22
|
+
|
23
|
+
class Queue < ArrayBlockingQueue
|
24
|
+
MS = TimeUnit::MILLISECONDS
|
25
|
+
|
26
|
+
# Create a new queue
|
27
|
+
# @param [Integer] size The size of the queue
|
28
|
+
# @param [Integer] timeout_in_ms How long to wait when trying to push or pop
|
29
|
+
# @return [Queue] the new queue
|
30
|
+
def initialize (size=5, timeout_in_ms = 5)
|
31
|
+
super(size)
|
32
|
+
@timeout = timeout_in_ms
|
33
|
+
end
|
34
|
+
|
35
|
+
# Try to add an object to the queue
|
36
|
+
# @param [Object] obj The object to push
|
37
|
+
# @return [Boolean] true on success, false on timeout
|
38
|
+
def push obj
|
39
|
+
self.offer obj, @timeout, MS
|
40
|
+
end
|
41
|
+
|
42
|
+
# Pop an object ouf of the queue
|
43
|
+
# @return [Object, nil] nil if it times out; the popped object otherwise
|
44
|
+
def pop
|
45
|
+
self.poll @timeout, MS
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# A class that encapsulates several enumerables (that respond to the same
|
51
|
+
# enumerable with the same arity) and allows you to call them as if they
|
52
|
+
# were a single enumerable (using multiple threads to draw from them, if
|
53
|
+
# desired)
|
54
|
+
class MultiEnum
|
55
|
+
include Enumerable
|
56
|
+
|
57
|
+
# The queue that acts as the common cache for objects pulled
|
58
|
+
# from each of the enumerables
|
59
|
+
attr_accessor :queue
|
60
|
+
|
61
|
+
# Create a new MultiEnum
|
62
|
+
# @param [Enumerable] enumerators A list of enumerators that you wish to act as a single enum
|
63
|
+
# @param [Symbol] iterator Which iterator to call against each enum
|
64
|
+
# @param [Integer] size The size of the underlying queue
|
65
|
+
# @param [Integer, nil] numthreads The number of threads to dedicate to pulling items
|
66
|
+
# off the enumerators and pushing them onto the shared queue. nil or zero implies one for
|
67
|
+
# each enumerator
|
68
|
+
# @return [Threach::MultiEnum] the new multi-enumerator
|
69
|
+
def initialize enumerators, iterator = :each, size = 5, numthreads=nil
|
70
|
+
@enum = enumerators
|
71
|
+
@iter = iterator
|
72
|
+
@size = size
|
73
|
+
@numthreads = (numthreads.nil? or numthreads == 0) ? enumerators.size : numthreads
|
74
|
+
@queue = Threach::Queue.new(@size)
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# Pull records out of the given enumerators using the number of threads
|
79
|
+
# specified at initialization. Order of items is, obviously, not
|
80
|
+
# guaranteed.
|
81
|
+
#
|
82
|
+
# Also obviously, the passed block need to be of the same arity as the
|
83
|
+
# enumerator symbol passed into the intializer.
|
84
|
+
#
|
85
|
+
# An uncaptured exception thrown by any of the enumerators will bring
|
86
|
+
# the whole thing crashing down.
|
87
|
+
def each &blk
|
88
|
+
@producers = []
|
89
|
+
tmn = -1
|
90
|
+
@enum.each_slice(@numthreads).each do |eslice|
|
91
|
+
tmn += 1
|
92
|
+
@producers << Thread.new(eslice, tmn) do |eslice, tmn|
|
93
|
+
Thread.current[:threach_multi_num] = "p#{tmn}"
|
94
|
+
begin
|
95
|
+
eslice.size.times do |i|
|
96
|
+
eslice[i].send(@iter) do |*x|
|
97
|
+
# puts "...pushing #{x}"
|
98
|
+
@queue.put "#{Thread.current[:threach_multi_num]}: #{x}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
@queue.put :threach_multi_eof
|
102
|
+
rescue Exception => e
|
103
|
+
@queue.put :threach_multi_eof
|
104
|
+
raise StopIteration.new "Error in #{eslice.inspect}: #{e.inspect}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
done = 0
|
110
|
+
|
111
|
+
while done < @numthreads
|
112
|
+
d = @queue.take
|
113
|
+
# puts "...pulling #{d}"
|
114
|
+
if d == :threach_multi_eof
|
115
|
+
done += 1
|
116
|
+
next
|
117
|
+
end
|
118
|
+
yield d
|
119
|
+
end
|
120
|
+
|
121
|
+
@producers.each {|p| p.join}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
# Enumerable is monkey-patched to provide two new methods: #threach and
|
128
|
+
# #mthreach.
|
129
|
+
module Enumerable
|
130
|
+
|
131
|
+
# Build up a MultiEnum from the calling object and run threach against
|
132
|
+
# it
|
133
|
+
# @param [Integer, nil] pthreads The number of producer threads to run within the
|
134
|
+
# created Threach::MultiEnum
|
135
|
+
# @param [Integer] threads The number of consumer threads to run in #threach
|
136
|
+
# @param [Symbol] iterator Which iterator to call (:each, :each_with_index, etc.)
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# [1..10, 'a'..'z'].mthreach(2,2) {|i| process_item(i)}
|
140
|
+
#
|
141
|
+
def mthreach(pthreads=nil, threads = 0, iterator = :each, &blk)
|
142
|
+
me = Threach::MultiEnum.new(self, iterator, threads * 3, pthreads)
|
143
|
+
me.send(:threach, threads, iterator, &blk)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Run the passed block using the given iterator using the given
|
147
|
+
# number of threads. If one of the consumer threads bails for any reason
|
148
|
+
# (break, throw an un-rescued error), the whole thing will shut down in an
|
149
|
+
# orderly fashion.
|
150
|
+
# @param [Integer] threads How many threads to use. 0 means to skip the whole
|
151
|
+
# threading thing completely and just directly call the indicated iterator
|
152
|
+
# @param [Symbol] iterator Which iterator to use (:each, :each_with_index, :each_line,
|
153
|
+
# etc.).
|
154
|
+
def threach(threads = 0, iterator = :each, &blk)
|
155
|
+
|
156
|
+
# With no extra threads, just spin up the passed iterator
|
157
|
+
if threads == 0
|
158
|
+
self.send(iterator, &blk)
|
159
|
+
else
|
160
|
+
# Get a java BlockingQueue for the producer to dump stuff into
|
161
|
+
bq = Threach::Queue.new(threads * 2) # capacity is twice the number of threads
|
162
|
+
|
163
|
+
# And another to store errors
|
164
|
+
errorq = Threach::Queue.new(threads + 1)
|
165
|
+
|
166
|
+
# A boolean to let us know if things are going wonky
|
167
|
+
bail = false
|
168
|
+
outofdata = false
|
169
|
+
|
170
|
+
# Build up a set of consumers
|
171
|
+
|
172
|
+
consumers = []
|
173
|
+
threads.times do |i|
|
174
|
+
consumers << Thread.new(i) do |i|
|
175
|
+
Thread.current[:threach_num] = i
|
176
|
+
begin
|
177
|
+
while true
|
178
|
+
obj = bq.pop
|
179
|
+
|
180
|
+
# Should we be bailing?
|
181
|
+
if bail
|
182
|
+
print "Thread #{Thread.current[:threach_num]}: BAIL!\n" if Threach::DEBUG
|
183
|
+
Thread.current[:threach_bail] = true
|
184
|
+
raise Threach::ThreachNotMyError.new, "bailing", nil
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
# If the return value is nil, it timed out. See if there's
|
189
|
+
# anything wrong, or if we've run out of work
|
190
|
+
if obj.nil?
|
191
|
+
if outofdata
|
192
|
+
Thread.current[:threach_outofdata] = true
|
193
|
+
raise Threach::ThreachEndOfRun.new, "out of work", nil
|
194
|
+
end
|
195
|
+
# otherwise, try to pop again
|
196
|
+
next
|
197
|
+
end
|
198
|
+
|
199
|
+
# Otherwise, do the work
|
200
|
+
blk.call(*obj)
|
201
|
+
end
|
202
|
+
|
203
|
+
rescue Threach::ThreachNotMyError => e
|
204
|
+
print "Thread #{Thread.current[:threach_num]}: Not my error\n" if Threach::DEBUG
|
205
|
+
Thread.current[:threach_bail] = true
|
206
|
+
# do nothing; wasn't my error, so I just bailed
|
207
|
+
|
208
|
+
rescue Threach::ThreachEndOfRun => e
|
209
|
+
print "Thread #{Thread.current[:threach_num]}: End of run\n" if Threach::DEBUG
|
210
|
+
Thread.current[:threach_bail] = true
|
211
|
+
# do nothing; everything exited normally
|
212
|
+
|
213
|
+
rescue Exception => e
|
214
|
+
print "Thread #{Thread.current[:threach_num]}: Exception #{e.inspect}: #{e.message}\n" if Threach::DEBUG
|
215
|
+
# Some other error; let everyone else know
|
216
|
+
bail = true
|
217
|
+
Thread.current[:threach_bail]
|
218
|
+
errorq.push e
|
219
|
+
ensure
|
220
|
+
# OK, I don't understand this, but I'm unable to catch org.jruby.exceptions.JumpException$BreakJump
|
221
|
+
# But if I get here and nothing else is set, that means I broke and need to deal with
|
222
|
+
# it accordingly
|
223
|
+
unless Thread.current[:threach_bail] or Thread.current[:threach_outofdata]
|
224
|
+
print "Thread #{Thread.current[:threach_num]}: broke out of loop\n" if Threach::DEBUG
|
225
|
+
bail = true
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
# Now, our producer
|
233
|
+
|
234
|
+
# Start running the given iterator and try to push stuff
|
235
|
+
|
236
|
+
begin
|
237
|
+
self.send(iterator) do |*x|
|
238
|
+
until successful_push = bq.push(x)
|
239
|
+
# if we're in here, we got a timeout. Check for errors
|
240
|
+
raise Threach::ThreachNotMyError.new, "bailing", nil if bail if bail
|
241
|
+
end
|
242
|
+
print "Queued #{x}\n" if Threach::DEBUG
|
243
|
+
end
|
244
|
+
|
245
|
+
# We're all done. Let 'em know
|
246
|
+
print "Setting outofdata to true\n" if Threach::DEBUG
|
247
|
+
outofdata = true
|
248
|
+
|
249
|
+
rescue NativeException => e
|
250
|
+
print "Producer rescuing native exception #{e.inspect}" if Threach::DEBUG
|
251
|
+
|
252
|
+
rescue Threach::ThreachNotMyError => e
|
253
|
+
print "Producer: not my error\n" if Threach::DEBUG
|
254
|
+
# do nothing. Not my error
|
255
|
+
|
256
|
+
rescue Exception => e
|
257
|
+
print "Producer: exception\n" if Threach::DEBUG
|
258
|
+
bail = true
|
259
|
+
errorq.push e
|
260
|
+
end
|
261
|
+
|
262
|
+
# Finally, #join the consumers
|
263
|
+
|
264
|
+
consumers.each {|t| t.join}
|
265
|
+
|
266
|
+
# Everything's done. If there's an error on the stack, raise it
|
267
|
+
if e = errorq.peek
|
268
|
+
print "Producer: raising #{e.inspect}\n" if Threach::DEBUG
|
269
|
+
raise e, e.message, nil
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
__END__
|
278
|
+
|
279
|
+
class DelayedEnum
|
280
|
+
include Enumerable
|
281
|
+
|
282
|
+
def initialize coll
|
283
|
+
@coll = coll
|
284
|
+
end
|
285
|
+
|
286
|
+
def each &blk
|
287
|
+
@coll.each do |i|
|
288
|
+
sleep 0.1
|
289
|
+
yield i
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
c = DelayedEnum.new((1..10).to_a)
|
295
|
+
d = DelayedEnum.new(('A'..'N').to_a)
|
296
|
+
e = DelayedEnum.new((20..30).to_a)
|
297
|
+
f = DelayedEnum.new(('m'..'z').to_a)
|
298
|
+
|
299
|
+
[c,d,e,f].mthreach(2,2) {|i| print "#{Thread.current[:threach_num]}: #{i}\n"}
|
300
|
+
|
data/samples.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
load 'lib/jruby_threach.rb'
|
2
|
+
|
3
|
+
# Simple: just do it
|
4
|
+
puts "\n\nProblem-free execution\n"
|
5
|
+
(1..10).threach(3) do |i|
|
6
|
+
print "#{Thread.current[:threach_num]}: #{i}\n"
|
7
|
+
(Thread.current[:threach_num] % 3) == 1 ? sleep(0.1): sleep(0.3)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Break out of it. If any one thread breaks, they all grind
|
11
|
+
# to a half
|
12
|
+
|
13
|
+
puts "\n\nBreak out of the block\n"
|
14
|
+
(1..10).threach(3) do |i|
|
15
|
+
print "#{Thread.current[:threach_num]}: #{i}\n"
|
16
|
+
break if i == 5
|
17
|
+
(Thread.current[:threach_num] % 3) == 1 ? sleep(0.1): sleep(0.3)
|
18
|
+
end
|
19
|
+
|
20
|
+
# We also capture errors that aren't handled in the block, stop
|
21
|
+
# everything, and then re-raise the error to the calling code
|
22
|
+
|
23
|
+
puts "\n\nStop execution on error and pass error to calling code\n"
|
24
|
+
begin
|
25
|
+
(1..10).threach(3) do |i|
|
26
|
+
print "#{Thread.current[:threach_num]}: #{i}\n"
|
27
|
+
raise RuntimeError.new, "oops", nil if i == 5
|
28
|
+
(Thread.current[:threach_num] % 3) == 1 ? sleep(0.1): sleep(0.3)
|
29
|
+
end
|
30
|
+
rescue RuntimeError => e
|
31
|
+
print "Caught an error that stopped execution\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Of course, if you deal with the error within the block,
|
35
|
+
# there's no problem.
|
36
|
+
|
37
|
+
puts "\n\nRescue error within passed block\n"
|
38
|
+
begin
|
39
|
+
(1..10).threach(3) do |i|
|
40
|
+
begin
|
41
|
+
print "#{Thread.current[:threach_num]}: #{i}\n"
|
42
|
+
raise RuntimeError.new, "oops", nil if i == 5
|
43
|
+
(Thread.current[:threach_num] % 3) == 1 ? sleep(0.1): sleep(0.3)
|
44
|
+
rescue RuntimeError => e
|
45
|
+
print "Caught error inside block; just ignore it and move on\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
rescue RuntimeError => e
|
49
|
+
print "Caught an error that stopped execution\n" # won't ever run
|
50
|
+
end
|
51
|
+
|
52
|
+
# Nesting example
|
53
|
+
files = ['Rakefile', 'README.rdoc', 'LICENSE.txt']
|
54
|
+
files.threach(3) do |f|
|
55
|
+
File.open(f).threach(2, :each_line) do |line|
|
56
|
+
print "#{f}: #{line.chomp[0..20]}\n"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'jruby_threach'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jruby_threach
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.3.0
|
6
|
+
platform: java
|
7
|
+
authors:
|
8
|
+
- BillDueber
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-30 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rspec
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.3.0
|
24
|
+
type: :development
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.6.0
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: bundler
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.0
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: jeweler
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.6.0
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: rcov
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: *id005
|
71
|
+
description: Run a block of code in multiple threads. Similar to threach, but with the ability to deal with breaks/exceptions (MRI can't because of unreliable timeouts).
|
72
|
+
email: bill@dueber.com
|
73
|
+
executables: []
|
74
|
+
|
75
|
+
extensions: []
|
76
|
+
|
77
|
+
extra_rdoc_files:
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.markdown
|
80
|
+
files:
|
81
|
+
- .document
|
82
|
+
- .rspec
|
83
|
+
- Gemfile
|
84
|
+
- LICENSE.txt
|
85
|
+
- README.markdown
|
86
|
+
- Rakefile
|
87
|
+
- lib/jruby_threach.rb
|
88
|
+
- samples.rb
|
89
|
+
- spec/jruby_threach_spec.rb
|
90
|
+
- spec/spec_helper.rb
|
91
|
+
has_rdoc: true
|
92
|
+
homepage: http://github.com/billdueber/jruby_threach
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
hash: -1042269137
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
version: "0"
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: "0"
|
115
|
+
requirements: []
|
116
|
+
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 1.6.2
|
119
|
+
signing_key:
|
120
|
+
specification_version: 3
|
121
|
+
summary: Very simply run a block of code using multiple threads under jruby
|
122
|
+
test_files: []
|
123
|
+
|