in_threads 1.2.2 → 1.3.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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YzcyOTliODQ1ZDMyYmY1NDRiMTEyZTc4ODI0ZGViNTQ3MDJmODNhMQ==
5
- data.tar.gz: !binary |-
6
- ZWNlZTE1ZjE5NmNlYzdiOWM4Y2YxNjJiMDVlYjVmMjQ2ZGY1OTM0MQ==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- MDJkYjFlZDRiMTJiZjc2OTU0NWM2NGJjMWU2MjQ3YzdkZDQ3OTkxZTY0N2Uw
10
- OWRjMTc4ODFjZTM5MTM1OGUxNDlkMTMxZGI2OTljZTRmZjAwYTZjY2I3OTMw
11
- ZDU0YTFhODA2YzFiOWNiZDlmNzBkYmQwZWU3MWJlOWRjYmI4MmU=
12
- data.tar.gz: !binary |-
13
- MDgzOWFkYmIwZjEzZDVlYjcyYTZlMjU4YzQwZTYyYThmNWM0MzExMjk4ZTY5
14
- NWIwOTlhMTA3Y2UyMjA0NzYxYTA5NTcyZWVlNmU1M2Q2M2E1ODRmNWU2OGU2
15
- ZjBlMzI0MTI2MWMzNTA3OWM2MDdkM2MxZWZlOGE3ZDc2NGQyNTU=
2
+ SHA1:
3
+ metadata.gz: 55a72b765291c267b859530ea8413d70201e45fc
4
+ data.tar.gz: 2227981ac7e0ed5c3e4b3a438a6087acfe427504
5
+ SHA512:
6
+ metadata.gz: 6eb1d9590ba97d801053e963772bdf69411e7b08c44c11c31f149ca71d2b0919b64bf07c7c73bda2748e17a99faa9abb3853bea7b161013c84c9b81336d466ba
7
+ data.tar.gz: dd1fa2f4e0d304a9f4f6a0ea33379fad3a28937f8d9e008e40e2e954a8d36ce613f3102d0259fd69154b4016045ac9cfed140f006235d5166d6b329bcc892e98
data/.rubocop.yml ADDED
@@ -0,0 +1,53 @@
1
+ AllCops:
2
+ Exclude:
3
+ - '*.gemspec'
4
+
5
+ Lint/EndAlignment:
6
+ AlignWith: variable
7
+
8
+ Metrics/ClassLength:
9
+ Max: 150
10
+
11
+ Metrics/MethodLength:
12
+ Max: 25
13
+
14
+ Style/AccessModifierIndentation:
15
+ EnforcedStyle: outdent
16
+
17
+ Style/CaseIndentation:
18
+ IndentWhenRelativeTo: end
19
+
20
+ Style/DotPosition:
21
+ EnforcedStyle: trailing
22
+
23
+ Style/DoubleNegation:
24
+ Enabled: false
25
+
26
+ Style/Encoding:
27
+ EnforcedStyle: when_needed
28
+
29
+ Style/HashSyntax:
30
+ EnforcedStyle: hash_rockets
31
+
32
+ Style/IfUnlessModifier:
33
+ MaxLineLength: 40
34
+
35
+ Style/IndentHash:
36
+ EnforcedStyle: consistent
37
+
38
+ Style/PercentLiteralDelimiters:
39
+ PreferredDelimiters:
40
+ '%w': '[]'
41
+ '%W': '[]'
42
+
43
+ Style/Semicolon:
44
+ AllowAsExpressionSeparator: true
45
+
46
+ Style/SpaceBeforeBlockBraces:
47
+ EnforcedStyle: no_space
48
+
49
+ Style/SpaceInsideHashLiteralBraces:
50
+ EnforcedStyle: no_space
51
+
52
+ Style/TrailingComma:
53
+ EnforcedStyleForMultiline: comma
data/.travis.yml CHANGED
@@ -4,7 +4,17 @@ rvm:
4
4
  - 1.9.2
5
5
  - 1.9.3
6
6
  - 2.0.0
7
+ - 2.1.0
8
+ - ruby-head
7
9
  - jruby-18mode
8
10
  - jruby-19mode
11
+ - jruby-head
9
12
  - ree
10
- script: "bundle exec rspec"
13
+ script:
14
+ - bundle exec rspec
15
+ - '! bundle show rubocop || bundle exec rubocop' # run rubocop only if bundled
16
+ matrix:
17
+ fast_finish: true
18
+ allow_failures:
19
+ - rvm: ruby-head
20
+ - rvm: jruby-head
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
data/README.markdown CHANGED
@@ -1,9 +1,13 @@
1
+ [![Gem Version](https://img.shields.io/gem/v/in_threads.svg?style=flat)](https://rubygems.org/gems/in_threads)
2
+ [![Build Status](https://img.shields.io/travis/toy/in_threads/master.svg?style=flat)](https://travis-ci.org/toy/in_threads)
3
+ [![Code Climate](https://img.shields.io/codeclimate/github/toy/in_threads.svg?style=flat)](https://codeclimate.com/github/toy/in_threads)
4
+ [![Dependency Status](https://img.shields.io/gemnasium/toy/in_threads.svg?style=flat)](https://gemnasium.com/toy/in_threads)
5
+ [![Inch CI](http://inch-ci.org/github/toy/in_threads.svg?branch=master&style=flat)](http://inch-ci.org/github/toy/in_threads)
6
+
1
7
  # in_threads
2
8
 
3
9
  Easily execute ruby code in parallel.
4
10
 
5
- [![Build Status](https://travis-ci.org/toy/in_threads.png?branch=master)](https://travis-ci.org/toy/in_threads)
6
-
7
11
  ## Installation
8
12
 
9
13
  gem install in_threads
data/in_threads.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'in_threads'
5
- s.version = '1.2.2'
5
+ s.version = '1.3.0'
6
6
  s.summary = %q{Execute ruby code in parallel}
7
7
  s.homepage = "http://github.com/toy/#{s.name}"
8
8
  s.authors = ['Ivan Kuchin']
@@ -16,4 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.require_paths = %w[lib]
17
17
 
18
18
  s.add_development_dependency 'rspec', '~> 3.0'
19
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('1.9.3')
20
+ s.add_development_dependency 'rubocop', '~> 0.26.0'
21
+ end
19
22
  end
data/lib/in_threads.rb CHANGED
@@ -1,16 +1,135 @@
1
1
  require 'thread'
2
+ require 'thwait'
2
3
  require 'delegate'
3
4
 
4
- class InThreads < Delegator
5
+ Enumerable.class_eval do
6
+ # Run enumerable method blocks in threads
7
+ #
8
+ # urls.in_threads.map do |url|
9
+ # url.fetch
10
+ # end
11
+ #
12
+ # Specify number of threads to use:
13
+ #
14
+ # files.in_threads(4).all? do |file|
15
+ # file.valid?
16
+ # end
17
+ #
18
+ # Passing block runs it against `each`
19
+ #
20
+ # urls.in_threads.each{ ... }
21
+ #
22
+ # is same as
23
+ #
24
+ # urls.in_threads{ ... }
25
+ def in_threads(thread_count = 10, &block)
26
+ InThreads.new(self, thread_count, &block)
27
+ end
28
+ end
29
+
30
+ # Run Enumerable methods with blocks in threads
31
+ class InThreads < SimpleDelegator
32
+ protected :__getobj__, :__setobj__
33
+
34
+ # Use ThreadsWait to limit number of threads
35
+ class ThreadLimiter
36
+ # Initialize with limit
37
+ def initialize(count)
38
+ @count = count
39
+ @waiter = ThreadsWait.new
40
+ end
41
+
42
+ # Without block behaves as `new`
43
+ # With block yields it with `self` and ensures running of `finalize`
44
+ def self.limit(count, &block)
45
+ limiter = new(count)
46
+ if block
47
+ begin
48
+ yield limiter
49
+ ensure
50
+ limiter.finalize
51
+ end
52
+ else
53
+ limiter
54
+ end
55
+ end
56
+
57
+ # Add thread to `ThreadsWait`, wait for finishing of one thread if limit
58
+ # reached
59
+ def <<(thread)
60
+ if @waiter.threads.length + 1 >= @count
61
+ @waiter.join(thread).join
62
+ else
63
+ @waiter.join_nowait(thread)
64
+ end
65
+ end
66
+
67
+ # Wait for waiting threads
68
+ def finalize
69
+ @waiter.all_waits(&:join)
70
+ end
71
+ end
72
+
73
+ # Yield objects of one enum in multiple places
74
+ class Splitter
75
+ # Enumerable using Queue
76
+ class Transfer
77
+ # Holds one object, for distinguishing eof
78
+ class Item
79
+ attr_reader :value
80
+
81
+ def initialize(value)
82
+ @value = value
83
+ end
84
+ end
85
+
86
+ include Enumerable
87
+
88
+ def initialize
89
+ @queue = Queue.new
90
+ end
91
+
92
+ def <<(object)
93
+ @queue << Item.new(object)
94
+ end
95
+
96
+ def finish
97
+ @queue << nil
98
+ end
99
+
100
+ def each
101
+ while (o = @queue.pop)
102
+ yield o.value
103
+ end
104
+ nil # non reusable
105
+ end
106
+ end
107
+
108
+ # Enums receiving items
109
+ attr_reader :enums
110
+
111
+ def initialize(enumerable, enum_count)
112
+ @enums = Array.new(enum_count){ Transfer.new }
113
+ @filler = Thread.new do
114
+ enumerable.each do |o|
115
+ @enums.each do |enum|
116
+ enum << o
117
+ end
118
+ end
119
+ @enums.each(&:finish)
120
+ end
121
+ end
122
+ end
123
+
5
124
  attr_reader :enumerable, :thread_count
6
125
  def initialize(enumerable, thread_count = 10, &block)
7
126
  super(enumerable)
8
127
  @enumerable, @thread_count = enumerable, thread_count.to_i
9
128
  unless enumerable.is_a?(Enumerable)
10
- raise ArgumentError.new('`enumerable` should include Enumerable.')
129
+ fail ArgumentError, '`enumerable` should include Enumerable.'
11
130
  end
12
131
  if thread_count < 2
13
- raise ArgumentError.new('`thread_count` can\'t be less than 2.')
132
+ fail ArgumentError, '`thread_count` can\'t be less than 2.'
14
133
  end
15
134
  each(&block) if block
16
135
  end
@@ -25,21 +144,21 @@ class InThreads < Delegator
25
144
  #
26
145
  # use :run_in_threads_consecutive, :for => %w[all? any? none? one?]
27
146
  #
28
- # <tt>:for</tt> is required
29
- # <tt>:ignore_undefined</tt> ignores methods which are not present in <tt>Enumerable.instance_methods</tt>
147
+ # `:for` is required
148
+ # `:ignore_undefined` ignores methods which are not present in
149
+ # `Enumerable.instance_methods`
30
150
  def use(runner, options)
31
151
  methods = Array(options[:for])
32
- raise 'no methods provided using :for option' if methods.empty?
152
+ fail 'no methods provided using :for option' if methods.empty?
33
153
  ignore_undefined = options[:ignore_undefined]
34
154
  enumerable_methods = Enumerable.instance_methods.map(&:to_s)
35
155
  methods.each do |method|
36
- unless ignore_undefined && !enumerable_methods.include?(method)
37
- class_eval <<-RUBY
38
- def #{method}(*args, &block)
39
- #{runner}(enumerable, :#{method}, *args, &block)
40
- end
41
- RUBY
42
- end
156
+ next if ignore_undefined && !enumerable_methods.include?(method)
157
+ class_eval <<-RUBY
158
+ def #{method}(*args, &block)
159
+ #{runner}(:#{method}, *args, &block)
160
+ end
161
+ RUBY
43
162
  end
44
163
  end
45
164
  end
@@ -71,10 +190,11 @@ class InThreads < Delegator
71
190
  chunk slice_before
72
191
  ], :ignore_undefined => true
73
192
 
74
- # Special case method, works by applying <tt>run_in_threads_consecutive</tt> with map on enumerable returned by blockless run
193
+ # Special case method, works by applying `run_in_threads_consecutive` with
194
+ # map on enumerable returned by blockless run
75
195
  def grep(*args, &block)
76
196
  if block
77
- run_in_threads_consecutive(enumerable.grep(*args), :map, &block)
197
+ self.class.new(enumerable.grep(*args), thread_count).map(&block)
78
198
  else
79
199
  enumerable.grep(*args)
80
200
  end
@@ -87,16 +207,8 @@ class InThreads < Delegator
87
207
 
88
208
  protected
89
209
 
90
- def __getobj__
91
- @enumerable
92
- end
93
-
94
- def __setobj__(obj)
95
- @enumerable = obj
96
- end
97
-
98
210
  # Use for methods which don't use block result
99
- def run_in_threads_return_original_enum(enumerable, method, *args, &block)
211
+ def run_in_threads_return_original_enum(method, *args, &block)
100
212
  if block
101
213
  ThreadLimiter.limit(thread_count) do |limiter|
102
214
  enumerable.send(method, *args) do |*block_args|
@@ -108,24 +220,25 @@ protected
108
220
  end
109
221
  end
110
222
 
111
- # Use for methods which do use block result and fire objects in same way as <tt>each</tt>
112
- def run_in_threads_consecutive(enumerable, method, *args, &block)
223
+ # Use for methods which do use block result
224
+ def run_in_threads_consecutive(method, *args, &block)
113
225
  if block
114
- begin
115
- enum_a, enum_b = Filler.new(enumerable, 2).extractors
116
- results = Queue.new
117
- runner = Thread.new do
118
- Thread.current.priority = -1
119
- ThreadLimiter.limit(thread_count) do |limiter|
120
- enum_a.each do |object|
121
- break if Thread.current[:stop]
122
- thread = Thread.new(object, &block)
123
- results << thread
124
- limiter << thread
125
- end
226
+ enum_a, enum_b = Splitter.new(enumerable, 2).enums
227
+ results = Queue.new
228
+ runner = Thread.new do
229
+ Thread.current.priority = -1
230
+ ThreadLimiter.limit(thread_count) do |limiter|
231
+ enum_a.each do |object|
232
+ break if Thread.current[:stop]
233
+ thread = Thread.new(object, &block)
234
+ results << thread
235
+ limiter << thread
126
236
  end
127
237
  end
128
- enum_b.send(method, *args) do |object|
238
+ end
239
+
240
+ begin
241
+ enum_b.send(method, *args) do |_object|
129
242
  results.pop.value
130
243
  end
131
244
  ensure
@@ -138,11 +251,7 @@ protected
138
251
  end
139
252
 
140
253
  # Use for methods which don't use blocks or can not use threads
141
- def run_without_threads(enumerable, method, *args, &block)
254
+ def run_without_threads(method, *args, &block)
142
255
  enumerable.send(method, *args, &block)
143
256
  end
144
257
  end
145
-
146
- require 'in_threads/enumerable'
147
- require 'in_threads/filler'
148
- require 'in_threads/thread_limiter'