concurrently 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +8 -3
  4. data/README.md +70 -60
  5. data/RELEASE_NOTES.md +16 -1
  6. data/Rakefile +98 -14
  7. data/concurrently.gemspec +16 -12
  8. data/ext/mruby/io.rb +1 -1
  9. data/guides/Overview.md +191 -66
  10. data/guides/Performance.md +300 -102
  11. data/guides/Troubleshooting.md +28 -28
  12. data/lib/Ruby/concurrently/proc/evaluation/error.rb +10 -0
  13. data/lib/all/concurrently/error.rb +0 -3
  14. data/lib/all/concurrently/evaluation.rb +8 -12
  15. data/lib/all/concurrently/event_loop.rb +1 -1
  16. data/lib/all/concurrently/event_loop/fiber.rb +3 -3
  17. data/lib/all/concurrently/event_loop/io_selector.rb +1 -1
  18. data/lib/all/concurrently/event_loop/run_queue.rb +29 -17
  19. data/lib/all/concurrently/proc.rb +13 -13
  20. data/lib/all/concurrently/proc/evaluation.rb +29 -29
  21. data/lib/all/concurrently/proc/evaluation/error.rb +13 -0
  22. data/lib/all/concurrently/proc/fiber.rb +3 -6
  23. data/lib/all/concurrently/version.rb +1 -1
  24. data/lib/all/io.rb +118 -41
  25. data/lib/all/kernel.rb +82 -29
  26. data/lib/mruby/concurrently/event_loop/io_selector.rb +46 -0
  27. data/lib/mruby/kernel.rb +1 -1
  28. data/mrbgem.rake +28 -17
  29. data/mruby_builds/build_config.rb +67 -0
  30. data/perf/Ruby/stage.rb +23 -0
  31. data/perf/benchmark_call_methods.rb +32 -0
  32. data/perf/benchmark_call_methods_waiting.rb +52 -0
  33. data/perf/benchmark_wait_methods.rb +38 -0
  34. data/perf/mruby/stage.rb +8 -0
  35. data/perf/profile_await_readable.rb +10 -0
  36. data/perf/{concurrent_proc_call.rb → profile_call.rb} +1 -5
  37. data/perf/{concurrent_proc_call_and_forget.rb → profile_call_and_forget.rb} +1 -5
  38. data/perf/{concurrent_proc_call_detached.rb → profile_call_detached.rb} +1 -5
  39. data/perf/{concurrent_proc_call_nonblock.rb → profile_call_nonblock.rb} +1 -5
  40. data/perf/profile_wait.rb +7 -0
  41. data/perf/stage.rb +47 -0
  42. data/perf/stage/benchmark.rb +47 -0
  43. data/perf/stage/benchmark/code_gen.rb +29 -0
  44. data/perf/stage/benchmark/code_gen/batch.rb +41 -0
  45. data/perf/stage/benchmark/code_gen/single.rb +38 -0
  46. metadata +27 -23
  47. data/ext/mruby/array.rb +0 -19
  48. data/lib/Ruby/concurrently/error.rb +0 -4
  49. data/perf/_shared/stage.rb +0 -33
  50. data/perf/concurrent_proc_calls.rb +0 -49
  51. data/perf/concurrent_proc_calls_awaiting.rb +0 -48
@@ -0,0 +1,10 @@
1
+ module Concurrently
2
+ class Proc::Evaluation
3
+ module RescueableError
4
+ # Ruby has additional error classes
5
+ [NoMemoryError, SecurityError].each do |error_class|
6
+ append_features error_class
7
+ end
8
+ end
9
+ end
10
+ end
@@ -4,7 +4,4 @@ module Concurrently
4
4
  #
5
5
  # The general error of this gem.
6
6
  class Error < StandardError; end
7
-
8
- # @private
9
- RESCUABLE_ERRORS = [ScriptError, StandardError, SystemStackError]
10
7
  end
@@ -3,13 +3,12 @@ module Concurrently
3
3
  # @since 1.0.0
4
4
  #
5
5
  # `Concurrently::Evaluation` represents the evaluation of the main thread
6
- # outside of any concurrent procs.
6
+ # outside of any concurrent evaluations.
7
7
  #
8
8
  # @note Evaluations are **not thread safe**. They are operating on a fiber.
9
9
  # Fibers cannot be resumed inside a thread they were not created in.
10
10
  #
11
- # An instance will be returned by {current} if called outside of any
12
- # concurrent procs.
11
+ # An instance will be returned by {current} if called by the root evaluation.
13
12
  class Evaluation
14
13
  # The evaluation that is currently running in the current thread.
15
14
  #
@@ -19,9 +18,9 @@ module Concurrently
19
18
  # @return [Evaluation]
20
19
  #
21
20
  # @example
22
- # concurrent_proc do
21
+ # concurrently do
23
22
  # Concurrently::Evaluation.current # => #<Concurrently::Proc::Evaluation:0x00000000e56910>
24
- # end.call_nonblock
23
+ # end
25
24
  #
26
25
  # Concurrently::Evaluation.current # => #<Concurrently::Evaluation0x00000000e5be10>
27
26
  def self.current
@@ -46,9 +45,6 @@ module Concurrently
46
45
  def waiting?
47
46
  @waiting
48
47
  end
49
-
50
- # @private
51
- DEFAULT_RESUME_OPTS = { deferred_only: true }.freeze
52
48
 
53
49
  # @note The exclamation mark in its name stands for: Watch out!
54
50
  # This method is potentially dangerous and can break stuff. It also
@@ -65,10 +61,10 @@ module Concurrently
65
61
  # actually awaiting has not happened yet:
66
62
  #
67
63
  # ```ruby
68
- # conproc = concurrent_proc do
64
+ # evaluation = concurrent_proc do
69
65
  # wait 1
70
66
  # await_resume!
71
- # end
67
+ # end.call_nonblock
72
68
  #
73
69
  # conproc.resume! # resumes the wait call prematurely
74
70
  # ```
@@ -98,9 +94,9 @@ module Concurrently
98
94
 
99
95
  # Cancel running the fiber if it has already been scheduled to run; but
100
96
  # only if it was scheduled with a time offset. This is used to cancel the
101
- # timeout of a wait operation if the waiting fiber is resume before the
97
+ # timeout of a wait operation if the waiting fiber is resumed before the
102
98
  # timeout is triggered.
103
- run_queue.cancel(self, DEFAULT_RESUME_OPTS)
99
+ run_queue.cancel(self, true)
104
100
 
105
101
  run_queue.schedule_immediately(self, result)
106
102
  :resumed
@@ -72,7 +72,7 @@ module Concurrently
72
72
 
73
73
  # @private
74
74
  #
75
- # Its run queue keeping track of and scheduling all concurrent procs
75
+ # Its run queue keeping track of and scheduling all evaluations
76
76
  attr_reader :run_queue
77
77
 
78
78
  # @private
@@ -15,7 +15,7 @@ module Concurrently
15
15
  io_selector.process_ready_in waiting_time if io_selector.awaiting?
16
16
 
17
17
  run_queue.process_pending
18
- elsif io_selector.awaiting? or waiting_time
18
+ elsif io_selector.awaiting? or waiting_time < Float::INFINITY
19
19
  io_selector.process_ready_in waiting_time
20
20
  else
21
21
  # Having no pending timeouts or IO events would make run this loop
@@ -24,12 +24,12 @@ module Concurrently
24
24
  # is complete. Therefore, we never reach this part of the code unless
25
25
  # there is a bug or it is messed around with the internals of this gem.
26
26
  raise Error, "Infinitely running event loop detected: There " <<
27
- "are no concurrent procs or fibers scheduled and no IOs to await."
27
+ "are no evaluations scheduled and no IOs to await."
28
28
  end
29
29
  end
30
30
  rescue Exception => e
31
31
  Concurrently::EventLoop.current.reinitialize!
32
- raise Error, "Event loop teared down by #{e.inspect}"
32
+ raise e
33
33
  end
34
34
  end
35
35
  end
@@ -9,7 +9,7 @@ module Concurrently
9
9
  end
10
10
 
11
11
  def awaiting?
12
- @evaluations.any?
12
+ @evaluations.size > 0
13
13
  end
14
14
 
15
15
  def await_reader(io, evaluation)
@@ -14,36 +14,41 @@ module Concurrently
14
14
  # The additional cart index exists so carts can be cancelled by their
15
15
  # evaluation. Cancelled carts have their evaluation set to false.
16
16
 
17
- DEFAULT_CANCEL_OPTS = { deferred_only: false }.freeze
18
-
19
17
  class Track < Array
20
18
  def bisect_left
21
19
  bsearch_index{ |item| yield item } || length
22
20
  end
21
+
22
+ def next
23
+ idx = size-1
24
+ while idx >= 0
25
+ return self[idx] if self[idx][EVALUATION]
26
+ idx -= 1
27
+ end
28
+ end
23
29
  end
24
30
 
25
31
  def initialize(loop)
26
32
  @loop = loop
27
- @cart_index = {}
28
33
  @deferred_track = Track.new
29
34
  @immediate_track = Track.new
30
35
  end
31
36
 
32
- def schedule_immediately(evaluation, result = nil)
37
+ def schedule_immediately(evaluation, result = nil, cancellable = true)
33
38
  cart = [evaluation, false, result]
34
- @cart_index[evaluation.hash] = cart
39
+ evaluation.instance_variable_set :@__cart__, cart if cancellable
35
40
  @immediate_track << cart
36
41
  end
37
42
 
38
43
  def schedule_deferred(evaluation, seconds, result = nil)
39
44
  cart = [evaluation, @loop.lifetime+seconds, result]
40
- @cart_index[evaluation.hash] = cart
45
+ evaluation.instance_variable_set :@__cart__, cart
41
46
  index = @deferred_track.bisect_left{ |tcart| tcart[TIME] <= cart[TIME] }
42
47
  @deferred_track.insert(index, cart)
43
48
  end
44
49
 
45
- def cancel(evaluation, opts = DEFAULT_CANCEL_OPTS)
46
- if (cart = @cart_index[evaluation.hash]) and (not opts[:deferred_only] or cart[TIME])
50
+ def cancel(evaluation, only_if_deferred = false)
51
+ if (cart = evaluation.instance_variable_get :@__cart__) and (not only_if_deferred or cart[TIME])
47
52
  cart[EVALUATION] = false
48
53
  end
49
54
  end
@@ -54,26 +59,31 @@ module Concurrently
54
59
  processing = @immediate_track
55
60
  @immediate_track = []
56
61
 
57
- if @deferred_track.any?
62
+ if @deferred_track.size > 0
58
63
  now = @loop.lifetime
59
64
  index = @deferred_track.bisect_left{ |cart| cart[TIME] <= now }
60
- @deferred_track.pop(@deferred_track.length-index).reverse_each do |cart|
61
- processing << cart
65
+
66
+ processable_count = @deferred_track.size-index
67
+ while processable_count > 0
68
+ processing << @deferred_track.pop
69
+ processable_count -= 1
62
70
  end
63
71
  end
64
72
 
65
73
  processing.each do |cart|
66
- @cart_index.delete cart[EVALUATION].hash
67
- resume_evaluation! cart[EVALUATION], cart[RESULT] if cart[EVALUATION]
74
+ evaluation = cart[EVALUATION]
75
+ resume_evaluation! evaluation, cart[RESULT] if evaluation
68
76
  end
69
77
  end
70
78
 
71
79
  def waiting_time
72
- if @immediate_track.any?
80
+ if @immediate_track.size > 0
73
81
  0
74
- elsif next_cart = @deferred_track.reverse_each.find{ |cart| cart[EVALUATION] }
75
- waiting_time = next_cart[TIME] - @loop.lifetime
82
+ elsif cart = @deferred_track.next
83
+ waiting_time = cart[TIME] - @loop.lifetime
76
84
  waiting_time < 0 ? 0 : waiting_time
85
+ else
86
+ Float::INFINITY
77
87
  end
78
88
  end
79
89
 
@@ -87,9 +97,11 @@ module Concurrently
87
97
  when Proc::Evaluation
88
98
  @current_evaluation = evaluation
89
99
  evaluation.fiber.resume result
90
- else
100
+ when Evaluation
91
101
  @current_evaluation = nil
92
102
  Fiber.yield result
103
+ else
104
+ raise Error, "#{evaluation.inspect} cannot be resumed"
93
105
  end
94
106
  ensure
95
107
  @current_evaluation = previous_evaluation
@@ -21,17 +21,17 @@ module Concurrently
21
21
  #
22
22
  # A `Concurrently::Proc` is like a regular Proc except its block of code is
23
23
  # evaluated concurrently. Its evaluation can wait for other stuff to happen
24
- # (e.g. result of another concurrent proc or readiness of an IO) without
25
- # blocking the execution of its thread.
24
+ # (e.g. result of evaluations or readiness of an IO) without blocking the
25
+ # execution of its thread.
26
26
  #
27
- # Errors raised inside concurrent procs are re-raised when getting their
28
- # result with {Evaluation#await_result}. They can also be watched by
27
+ # Errors raised inside concurrent evaluations are re-raised when getting
28
+ # their result with {Evaluation#await_result}. They can also be watched by
29
29
  # registering callbacks for the `:error` event as shown in the example below.
30
- # This is useful as a central hook to all errors inside concurrent procs for
31
- # monitoring or logging purposes. Also, concurrent procs evaluated with
32
- # {Kernel#concurrently} resp. {Proc#call_and_forget} are run in the
33
- # background and will fail silently. The callbacks are the only way to be
34
- # notified about errors inside them.
30
+ # This is useful as a central hook to all errors inside concurrent
31
+ # evaluations for monitoring or logging purposes. Also, concurrent procs
32
+ # evaluated with {Proc#call_and_forget} are evaluated in the background with
33
+ # no access to their evaluation and will fail silently. The callbacks are the
34
+ # only way to be notified about errors inside them.
35
35
  #
36
36
  # The callbacks can be registered for all procs or only for one specific
37
37
  # proc:
@@ -187,6 +187,9 @@ module Concurrently
187
187
  # thread of execution and schedules its start during the next iteration of
188
188
  # the event loop.
189
189
  #
190
+ # To execute code this way you can also use the shortcut
191
+ # {Kernel#concurrently}.
192
+ #
190
193
  # @return [Evaluation]
191
194
  #
192
195
  # @example
@@ -208,9 +211,6 @@ module Concurrently
208
211
  # because we save creating an {Evaluation} instance this is slightly faster
209
212
  # than {#call_detached}.
210
213
  #
211
- # To execute code this way you can also use the shortcut
212
- # {Kernel#concurrently}.
213
- #
214
214
  # @return [nil]
215
215
  #
216
216
  # @example
@@ -225,7 +225,7 @@ module Concurrently
225
225
  event_loop = EventLoop.current
226
226
  # run without creating an Evaluation object at first. It will be created
227
227
  # if the proc needs to wait for something.
228
- event_loop.run_queue.schedule_immediately event_loop.proc_fiber_pool.take_fiber, [self, args]
228
+ event_loop.run_queue.schedule_immediately event_loop.proc_fiber_pool.take_fiber, [self, args], false
229
229
 
230
230
  nil
231
231
  end
@@ -8,8 +8,8 @@ module Concurrently
8
8
  # @note Evaluations are **not thread safe**. They are operating on a fiber.
9
9
  # Fibers cannot be resumed inside a thread they were not created in.
10
10
  #
11
- # An instance will be returned by {Evaluation.current} if called from inside
12
- # a concurrent proc. It will also be returned by every call of
11
+ # An instance will be returned by {Evaluation.current} if called by code
12
+ # inside a concurrent proc. It will also be returned by every call of
13
13
  # {Concurrently::Proc#call_detached} and also by
14
14
  # {Concurrently::Proc#call_nonblock} if the evaluation cannot be concluded in
15
15
  # one go and needs to wait.
@@ -29,7 +29,7 @@ module Concurrently
29
29
  # @return [value]
30
30
  #
31
31
  # @example
32
- # evaluation = concurrent_proc{ :result }.call_detached
32
+ # evaluation = concurrently{ :result }
33
33
  # evaluation[:key] = :value
34
34
  # evaluation[:key] # => :value
35
35
  def []=(key, value)
@@ -42,7 +42,7 @@ module Concurrently
42
42
  # @return [Object] the stored value
43
43
  #
44
44
  # @example
45
- # evaluation = concurrent_proc{ :result }.call_detached
45
+ # evaluation = concurrently{ :result }
46
46
  # evaluation[:key] = :value
47
47
  # evaluation[:key] # => :value
48
48
  def [](key)
@@ -55,7 +55,7 @@ module Concurrently
55
55
  # @return [Boolean]
56
56
  #
57
57
  # @example
58
- # evaluation = concurrent_proc{ :result }.call_detached
58
+ # evaluation = concurrently{ :result }
59
59
  # evaluation[:key] = :value
60
60
  # evaluation.key? :key # => true
61
61
  # evaluation.key? :another_key # => false
@@ -68,7 +68,7 @@ module Concurrently
68
68
  # @return [Array]
69
69
  #
70
70
  # @example
71
- # evaluation = concurrent_proc{ :result }.call_detached
71
+ # evaluation = concurrently{ :result }
72
72
  # evaluation[:key1] = :value1
73
73
  # evaluation[:key2] = :value2
74
74
  # evaluation.keys # => [:key1, :key2]
@@ -91,14 +91,14 @@ module Concurrently
91
91
  # @raise [Concurrently::Evaluation::TimeoutError] if a given maximum waiting time
92
92
  # is exceeded and no custom timeout result is given.
93
93
  #
94
- # @example Waiting inside another concurrent proc
94
+ # @example Waiting inside another concurrent evaluation
95
95
  # # Control flow is indicated by (N)
96
96
  #
97
97
  # # (1)
98
- # evaluation = concurrent_proc do
98
+ # evaluation = concurrently do
99
99
  # # (4)
100
100
  # :result
101
- # end.call_detached
101
+ # end
102
102
  #
103
103
  # # (2)
104
104
  # concurrent_proc do
@@ -108,41 +108,41 @@ module Concurrently
108
108
  # end.call # => :result
109
109
  # # (6)
110
110
  #
111
- # @example Waiting outside a concurrent proc
111
+ # @example Waiting outside a concurrent evaluation
112
112
  # # Control flow is indicated by (N)
113
113
  #
114
114
  # # (1)
115
- # evaluation = concurrent_proc do
116
- # # (3)
115
+ # evaluation = concurrently do
116
+ # # (3)
117
117
  # :result
118
- # end.call_detached
118
+ # end
119
119
  #
120
120
  # # (2)
121
121
  # evaluation.await_result # => :result
122
122
  # # (4)
123
123
  #
124
124
  # @example Waiting with a timeout
125
- # evaluation = concurrent_proc do
125
+ # evaluation = concurrently do
126
126
  # wait 1
127
127
  # :result
128
- # end.call_detached
128
+ # end
129
129
  #
130
130
  # evaluation.await_result within: 0.1
131
- # # => raises a TimeoutError after 0.1 second
131
+ # # => raises a TimeoutError after 0.1 seconds
132
132
  #
133
133
  # @example Waiting with a timeout and a timeout result
134
- # evaluation = concurrent_proc do
134
+ # evaluation = concurrently do
135
135
  # wait 1
136
136
  # :result
137
- # end.call_detached
137
+ # end
138
138
  #
139
139
  # evaluation.await_result within: 0.1, timeout_result: false
140
- # # => returns false after 0.1 second
140
+ # # => returns false after 0.1 seconds
141
141
  #
142
142
  # @example When the evaluation raises or returns an error
143
- # evaluation = concurrent_proc do
143
+ # evaluation = concurrently do
144
144
  # RuntimeError.new("self destruct!") # equivalent: raise "self destruct!"
145
- # end.call_detached
145
+ # end
146
146
  #
147
147
  # evaluation.await_result # => raises "self destruct!"
148
148
  #
@@ -156,17 +156,17 @@ module Concurrently
156
156
  # @yieldreturn [Object] a (potentially) transformed result
157
157
  #
158
158
  # @example Transforming a result
159
- # evaluation = concurrent_proc do
159
+ # evaluation = concurrently do
160
160
  # :result
161
- # end.call_detached
161
+ # end
162
162
  #
163
163
  # evaluation.await_result{ |result| "transformed_#{result}" }
164
164
  # # => "transformed_result"
165
165
  #
166
166
  # @example Validating a result
167
- # evaluation = concurrent_proc do
167
+ # evaluation = concurrently do
168
168
  # :invalid_result
169
- # end.call_detached
169
+ # end
170
170
  #
171
171
  # evaluation.await_result{ |result| raise "invalid result" if result != :result }
172
172
  # # => raises "invalid result"
@@ -176,7 +176,7 @@ module Concurrently
176
176
  else
177
177
  result = begin
178
178
  evaluation = Concurrently::Evaluation.current
179
- @awaiting_result.store evaluation, true
179
+ @awaiting_result.store evaluation, false
180
180
  await_resume! opts
181
181
  rescue Exception => error
182
182
  error
@@ -210,12 +210,12 @@ module Concurrently
210
210
  # # Control flow is indicated by (N)
211
211
  #
212
212
  # # (1)
213
- # evaluation = concurrent_proc do
213
+ # evaluation = concurrently do
214
214
  # # (4)
215
215
  # wait 1
216
216
  # # never reached
217
217
  # :result
218
- # end.call_nonblock
218
+ # end
219
219
  #
220
220
  # # (2)
221
221
  # concurrently do
@@ -239,7 +239,7 @@ module Concurrently
239
239
  @fiber.resume @fiber
240
240
  end
241
241
 
242
- @awaiting_result.each_key{ |evaluation| evaluation.resume! result }
242
+ @awaiting_result.each{ |evaluation, override| evaluation.resume! (override or result) }
243
243
  :concluded
244
244
  end
245
245
  end