concurrently 1.0.1 → 1.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.
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