concurrently 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +8 -3
- data/README.md +70 -60
- data/RELEASE_NOTES.md +16 -1
- data/Rakefile +98 -14
- data/concurrently.gemspec +16 -12
- data/ext/mruby/io.rb +1 -1
- data/guides/Overview.md +191 -66
- data/guides/Performance.md +300 -102
- data/guides/Troubleshooting.md +28 -28
- data/lib/Ruby/concurrently/proc/evaluation/error.rb +10 -0
- data/lib/all/concurrently/error.rb +0 -3
- data/lib/all/concurrently/evaluation.rb +8 -12
- data/lib/all/concurrently/event_loop.rb +1 -1
- data/lib/all/concurrently/event_loop/fiber.rb +3 -3
- data/lib/all/concurrently/event_loop/io_selector.rb +1 -1
- data/lib/all/concurrently/event_loop/run_queue.rb +29 -17
- data/lib/all/concurrently/proc.rb +13 -13
- data/lib/all/concurrently/proc/evaluation.rb +29 -29
- data/lib/all/concurrently/proc/evaluation/error.rb +13 -0
- data/lib/all/concurrently/proc/fiber.rb +3 -6
- data/lib/all/concurrently/version.rb +1 -1
- data/lib/all/io.rb +118 -41
- data/lib/all/kernel.rb +82 -29
- data/lib/mruby/concurrently/event_loop/io_selector.rb +46 -0
- data/lib/mruby/kernel.rb +1 -1
- data/mrbgem.rake +28 -17
- data/mruby_builds/build_config.rb +67 -0
- data/perf/Ruby/stage.rb +23 -0
- data/perf/benchmark_call_methods.rb +32 -0
- data/perf/benchmark_call_methods_waiting.rb +52 -0
- data/perf/benchmark_wait_methods.rb +38 -0
- data/perf/mruby/stage.rb +8 -0
- data/perf/profile_await_readable.rb +10 -0
- data/perf/{concurrent_proc_call.rb → profile_call.rb} +1 -5
- data/perf/{concurrent_proc_call_and_forget.rb → profile_call_and_forget.rb} +1 -5
- data/perf/{concurrent_proc_call_detached.rb → profile_call_detached.rb} +1 -5
- data/perf/{concurrent_proc_call_nonblock.rb → profile_call_nonblock.rb} +1 -5
- data/perf/profile_wait.rb +7 -0
- data/perf/stage.rb +47 -0
- data/perf/stage/benchmark.rb +47 -0
- data/perf/stage/benchmark/code_gen.rb +29 -0
- data/perf/stage/benchmark/code_gen/batch.rb +41 -0
- data/perf/stage/benchmark/code_gen/single.rb +38 -0
- metadata +27 -23
- data/ext/mruby/array.rb +0 -19
- data/lib/Ruby/concurrently/error.rb +0 -4
- data/perf/_shared/stage.rb +0 -33
- data/perf/concurrent_proc_calls.rb +0 -49
- data/perf/concurrent_proc_calls_awaiting.rb +0 -48
@@ -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
|
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
|
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
|
-
#
|
21
|
+
# concurrently do
|
23
22
|
# Concurrently::Evaluation.current # => #<Concurrently::Proc::Evaluation:0x00000000e56910>
|
24
|
-
# end
|
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
|
-
#
|
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
|
97
|
+
# timeout of a wait operation if the waiting fiber is resumed before the
|
102
98
|
# timeout is triggered.
|
103
|
-
run_queue.cancel(self,
|
99
|
+
run_queue.cancel(self, true)
|
104
100
|
|
105
101
|
run_queue.schedule_immediately(self, result)
|
106
102
|
:resumed
|
@@ -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
|
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
|
32
|
+
raise e
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -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
|
-
|
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
|
-
|
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,
|
46
|
-
if (cart =
|
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.
|
62
|
+
if @deferred_track.size > 0
|
58
63
|
now = @loop.lifetime
|
59
64
|
index = @deferred_track.bisect_left{ |cart| cart[TIME] <= now }
|
60
|
-
|
61
|
-
|
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
|
-
|
67
|
-
resume_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.
|
80
|
+
if @immediate_track.size > 0
|
73
81
|
0
|
74
|
-
elsif
|
75
|
-
waiting_time =
|
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
|
-
|
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
|
25
|
-
#
|
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
|
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
|
31
|
-
# monitoring or logging purposes. Also, concurrent procs
|
32
|
-
#
|
33
|
-
#
|
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
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
94
|
+
# @example Waiting inside another concurrent evaluation
|
95
95
|
# # Control flow is indicated by (N)
|
96
96
|
#
|
97
97
|
# # (1)
|
98
|
-
# evaluation =
|
98
|
+
# evaluation = concurrently do
|
99
99
|
# # (4)
|
100
100
|
# :result
|
101
|
-
# end
|
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
|
111
|
+
# @example Waiting outside a concurrent evaluation
|
112
112
|
# # Control flow is indicated by (N)
|
113
113
|
#
|
114
114
|
# # (1)
|
115
|
-
# evaluation =
|
116
|
-
#
|
115
|
+
# evaluation = concurrently do
|
116
|
+
# # (3)
|
117
117
|
# :result
|
118
|
-
# end
|
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 =
|
125
|
+
# evaluation = concurrently do
|
126
126
|
# wait 1
|
127
127
|
# :result
|
128
|
-
# end
|
128
|
+
# end
|
129
129
|
#
|
130
130
|
# evaluation.await_result within: 0.1
|
131
|
-
# # => raises a TimeoutError after 0.1
|
131
|
+
# # => raises a TimeoutError after 0.1 seconds
|
132
132
|
#
|
133
133
|
# @example Waiting with a timeout and a timeout result
|
134
|
-
# evaluation =
|
134
|
+
# evaluation = concurrently do
|
135
135
|
# wait 1
|
136
136
|
# :result
|
137
|
-
# end
|
137
|
+
# end
|
138
138
|
#
|
139
139
|
# evaluation.await_result within: 0.1, timeout_result: false
|
140
|
-
# # => returns false after 0.1
|
140
|
+
# # => returns false after 0.1 seconds
|
141
141
|
#
|
142
142
|
# @example When the evaluation raises or returns an error
|
143
|
-
# evaluation =
|
143
|
+
# evaluation = concurrently do
|
144
144
|
# RuntimeError.new("self destruct!") # equivalent: raise "self destruct!"
|
145
|
-
# end
|
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 =
|
159
|
+
# evaluation = concurrently do
|
160
160
|
# :result
|
161
|
-
# end
|
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 =
|
167
|
+
# evaluation = concurrently do
|
168
168
|
# :invalid_result
|
169
|
-
# end
|
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,
|
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 =
|
213
|
+
# evaluation = concurrently do
|
214
214
|
# # (4)
|
215
215
|
# wait 1
|
216
216
|
# # never reached
|
217
217
|
# :result
|
218
|
-
# end
|
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.
|
242
|
+
@awaiting_result.each{ |evaluation, override| evaluation.resume! (override or result) }
|
243
243
|
:concluded
|
244
244
|
end
|
245
245
|
end
|