jruby_threach 0.3.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
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
@@ -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.
@@ -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
+
@@ -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
+
@@ -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
+
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "JrubyThreach" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -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
+