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.
- 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
|