concurrent-ruby-edge 0.2.4 → 0.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 +4 -4
- data/README.md +28 -38
- data/lib/concurrent-edge.rb +6 -2
- data/lib/concurrent/actor.rb +10 -6
- data/lib/concurrent/actor/behaviour/sets_results.rb +3 -3
- data/lib/concurrent/actor/behaviour/termination.rb +7 -7
- data/lib/concurrent/actor/core.rb +4 -5
- data/lib/concurrent/actor/envelope.rb +2 -2
- data/lib/concurrent/actor/errors.rb +1 -1
- data/lib/concurrent/actor/reference.rb +7 -7
- data/lib/concurrent/actor/utils/pool.rb +2 -2
- data/lib/concurrent/edge/cancellation.rb +137 -0
- data/lib/concurrent/edge/lock_free_linked_set.rb +1 -1
- data/lib/concurrent/edge/lock_free_queue.rb +116 -0
- data/lib/concurrent/edge/lock_free_stack.rb +90 -68
- data/lib/concurrent/edge/old_channel_integration.rb +54 -0
- data/lib/concurrent/edge/promises.rb +2080 -0
- data/lib/concurrent/edge/throttle.rb +185 -0
- metadata +13 -8
- data/lib/concurrent/edge/future.rb +0 -1427
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 051e8d02906d153d112b5f2e2c86948d3cf0fd26
|
4
|
+
data.tar.gz: f4a53c06cfbf931dfc11245be8dabdf579d0e361
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b8490d330375764f70d70f212de4697dd3fbf83b0b4a7da1ffba7c86599fc64df3c4f25801ea0531970a04d2b62e04bad7701df9ce25afd24779dbd6b54d0fd
|
7
|
+
data.tar.gz: 6fc402ca7a4434e2b8e97a71fd351c81bbf8ff9467ccceb8159c78993fc8219e032704644cfbb965a34879aa5922bddc8d96f5f78343d0b0834602340d122a52
|
data/README.md
CHANGED
@@ -9,39 +9,28 @@
|
|
9
9
|
[](http://opensource.org/licenses/MIT)
|
10
10
|
[-devs%20%26%20users-brightgreen.svg)](https://gitter.im/ruby-concurrency/concurrent-ruby)
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
<li>Support features that make sense in Ruby</li>
|
35
|
-
<li>Exclude features that don't make sense in Ruby</li>
|
36
|
-
<li>Be small, lean, and loosely coupled</li>
|
37
|
-
</ul>
|
38
|
-
</p>
|
39
|
-
</td>
|
40
|
-
<td align="right" valign="top">
|
41
|
-
<img src="https://raw.githubusercontent.com/ruby-concurrency/concurrent-ruby/master/doc/logo/concurrent-ruby-logo-300x300.png"/>
|
42
|
-
</td>
|
43
|
-
</tr>
|
44
|
-
</table>
|
12
|
+
Modern concurrency tools for Ruby. Inspired by
|
13
|
+
[Erlang](http://www.erlang.org/doc/reference_manual/processes.html),
|
14
|
+
[Clojure](http://clojure.org/concurrent_programming),
|
15
|
+
[Scala](http://akka.io/),
|
16
|
+
[Haskell](http://www.haskell.org/haskellwiki/Applications_and_libraries/Concurrency_and_parallelism#Concurrent_Haskell),
|
17
|
+
[F#](http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx),
|
18
|
+
[C#](http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx),
|
19
|
+
[Java](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html),
|
20
|
+
and classic concurrency patterns.
|
21
|
+
|
22
|
+
<img src="https://raw.githubusercontent.com/ruby-concurrency/concurrent-ruby/master/doc/logo/concurrent-ruby-logo-300x300.png" align="right" style="margin-left: 20px;" />
|
23
|
+
|
24
|
+
The design goals of this gem are:
|
25
|
+
|
26
|
+
* Be an 'unopinionated' toolbox that provides useful utilities without debating which is better or why
|
27
|
+
* Remain free of external gem dependencies
|
28
|
+
* Stay true to the spirit of the languages providing inspiration
|
29
|
+
* But implement in a way that makes sense for Ruby
|
30
|
+
* Keep the semantics as idiomatic Ruby as possible
|
31
|
+
* Support features that make sense in Ruby
|
32
|
+
* Exclude features that don't make sense in Ruby
|
33
|
+
* Be small, lean, and loosely coupled
|
45
34
|
|
46
35
|
### Supported Ruby versions
|
47
36
|
|
@@ -127,13 +116,13 @@ These features are under active development and may change frequently. They are
|
|
127
116
|
keep backward compatibility (there may also lack tests and documentation). Semantic versions will
|
128
117
|
be obeyed though. Features developed in `concurrent-ruby-edge` are expected to move to `concurrent-ruby` when final.
|
129
118
|
|
130
|
-
* [
|
131
|
-
Implements the Actor Model, where concurrent actors exchange messages.
|
132
|
-
* [New Future Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/FutureShortcuts.html):
|
119
|
+
* [Promises Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promises.html):
|
133
120
|
Unified implementation of futures and promises which combines features of previous `Future`,
|
134
121
|
`Promise`, `IVar`, `Event`, `dataflow`, `Delay`, and `TimerTask` into a single framework. It extensively uses the
|
135
122
|
new synchronization layer to make all the features **non-blocking** and **lock-free**, with the exception of obviously blocking
|
136
123
|
operations like `#wait`, `#value`. It also offers better performance.
|
124
|
+
* [Actor](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor.html):
|
125
|
+
Implements the Actor Model, where concurrent actors exchange messages.
|
137
126
|
* [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/Channel.html):
|
138
127
|
Communicating Sequential Processes ([CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)).
|
139
128
|
Functionally equivalent to Go [channels](https://tour.golang.org/concurrency/2) with additional
|
@@ -141,15 +130,16 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
|
|
141
130
|
* [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyRegister.html)
|
142
131
|
* [AtomicMarkableReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/AtomicMarkableReference.html)
|
143
132
|
* [LockFreeLinkedSet](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/LockFreeLinkedSet.html)
|
144
|
-
* [LockFreeStack](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/
|
133
|
+
* [LockFreeStack](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LockFreeStack.html)
|
145
134
|
|
146
135
|
#### Statuses:
|
147
136
|
|
148
137
|
*Why are these not in core?*
|
149
138
|
|
139
|
+
- **Promises Framework** - They are being finalized to be able to be moved to core. They'll deprecate old
|
140
|
+
implementation.
|
150
141
|
- **Actor** - Partial documentation and tests; depends on new future/promise framework; stability is good.
|
151
142
|
- **Channel** - Brand new implementation; partial documentation and tests; stability is good.
|
152
|
-
- **Future/Promise Framework** - API changes; partial documentation and tests; stability is good.
|
153
143
|
- **LazyRegister** - Missing documentation and tests.
|
154
144
|
- **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** - Need real world battle testing.
|
155
145
|
|
data/lib/concurrent-edge.rb
CHANGED
@@ -6,7 +6,11 @@ require 'concurrent/channel'
|
|
6
6
|
require 'concurrent/exchanger'
|
7
7
|
require 'concurrent/lazy_register'
|
8
8
|
|
9
|
-
require 'concurrent/edge/future'
|
10
|
-
require 'concurrent/edge/lock_free_stack'
|
11
9
|
require 'concurrent/edge/atomic_markable_reference'
|
12
10
|
require 'concurrent/edge/lock_free_linked_set'
|
11
|
+
require 'concurrent/edge/lock_free_queue'
|
12
|
+
require 'concurrent/edge/lock_free_stack'
|
13
|
+
|
14
|
+
require 'concurrent/edge/promises'
|
15
|
+
require 'concurrent/edge/cancellation'
|
16
|
+
require 'concurrent/edge/throttle'
|
data/lib/concurrent/actor.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'concurrent/configuration'
|
2
2
|
require 'concurrent/executor/serialized_execution'
|
3
3
|
require 'concurrent/synchronization'
|
4
|
-
require 'concurrent/edge/
|
4
|
+
require 'concurrent/edge/promises'
|
5
5
|
|
6
6
|
module Concurrent
|
7
7
|
# TODO https://github.com/celluloid/celluloid/wiki/Supervision-Groups ?
|
@@ -34,8 +34,8 @@ module Concurrent
|
|
34
34
|
Thread.current[:__current_actor__]
|
35
35
|
end
|
36
36
|
|
37
|
-
@root = Concurrent.delay do
|
38
|
-
Core.new(parent: nil, name: '/', class: Root, initialized: future = Concurrent.
|
37
|
+
@root = Concurrent::Promises.delay do
|
38
|
+
Core.new(parent: nil, name: '/', class: Root, initialized: future = Concurrent::Promises.resolvable_future).reference.tap do
|
39
39
|
future.wait!
|
40
40
|
end
|
41
41
|
end
|
@@ -65,16 +65,20 @@ module Concurrent
|
|
65
65
|
# @param args see {.to_spawn_options}
|
66
66
|
# @return [Reference] never the actual actor
|
67
67
|
def self.spawn(*args, &block)
|
68
|
+
options = to_spawn_options(*args)
|
69
|
+
if options[:executor] && options[:executor].is_a?(ImmediateExecutor)
|
70
|
+
raise ArgumentError, 'ImmediateExecutor is not supported'
|
71
|
+
end
|
68
72
|
if Actor.current
|
69
|
-
Core.new(
|
73
|
+
Core.new(options.merge(parent: Actor.current), &block).reference
|
70
74
|
else
|
71
|
-
root.ask([:spawn,
|
75
|
+
root.ask([:spawn, options, block]).value!
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
75
79
|
# as {.spawn} but it'll block until actor is initialized or it'll raise exception on error
|
76
80
|
def self.spawn!(*args, &block)
|
77
|
-
spawn(to_spawn_options(*args).merge(initialized: future = Concurrent.
|
81
|
+
spawn(to_spawn_options(*args).merge(initialized: future = Concurrent::Promises.resolvable_future), &block).tap { future.wait! }
|
78
82
|
end
|
79
83
|
|
80
84
|
# @overload to_spawn_options(context_class, name, *args)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Concurrent
|
2
2
|
module Actor
|
3
3
|
module Behaviour
|
4
|
-
# Collects returning value and sets the
|
4
|
+
# Collects returning value and sets the ResolvableFuture in the {Envelope} or error on failure.
|
5
5
|
class SetResults < Abstract
|
6
6
|
attr_reader :error_strategy
|
7
7
|
|
@@ -13,7 +13,7 @@ module Concurrent
|
|
13
13
|
def on_envelope(envelope)
|
14
14
|
result = pass envelope
|
15
15
|
if result != MESSAGE_PROCESSED && !envelope.future.nil?
|
16
|
-
envelope.future.
|
16
|
+
envelope.future.fulfill result
|
17
17
|
log(DEBUG) { "finished processing of #{envelope.message.inspect}"}
|
18
18
|
end
|
19
19
|
nil
|
@@ -29,7 +29,7 @@ module Concurrent
|
|
29
29
|
else
|
30
30
|
raise
|
31
31
|
end
|
32
|
-
envelope.future.
|
32
|
+
envelope.future.reject error unless envelope.future.nil?
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -14,8 +14,8 @@ module Concurrent
|
|
14
14
|
|
15
15
|
def initialize(core, subsequent, core_options, trapping = false, terminate_children = true)
|
16
16
|
super core, subsequent, core_options
|
17
|
-
@terminated = Concurrent.
|
18
|
-
@public_terminated = @terminated.
|
17
|
+
@terminated = Concurrent::Promises.resolvable_future
|
18
|
+
@public_terminated = @terminated.with_hidden_resolvable
|
19
19
|
@trapping = trapping
|
20
20
|
@terminate_children = terminate_children
|
21
21
|
end
|
@@ -23,7 +23,7 @@ module Concurrent
|
|
23
23
|
# @note Actor rejects envelopes when terminated.
|
24
24
|
# @return [true, false] if actor is terminated
|
25
25
|
def terminated?
|
26
|
-
@terminated.
|
26
|
+
@terminated.resolved?
|
27
27
|
end
|
28
28
|
|
29
29
|
def trapping?
|
@@ -62,15 +62,15 @@ module Concurrent
|
|
62
62
|
def terminate!(reason = nil, envelope = nil)
|
63
63
|
return true if terminated?
|
64
64
|
|
65
|
-
self_termination = Concurrent.
|
65
|
+
self_termination = Concurrent::Promises.resolved_future(reason.nil?, reason.nil? || nil, reason)
|
66
66
|
all_terminations = if @terminate_children
|
67
|
-
Concurrent.zip(*children.map { |ch| ch.ask(:terminate!) }, self_termination)
|
67
|
+
Concurrent::Promises.zip(*children.map { |ch| ch.ask(:terminate!) }, self_termination)
|
68
68
|
else
|
69
69
|
self_termination
|
70
70
|
end
|
71
71
|
|
72
|
-
all_terminations.
|
73
|
-
all_terminations.
|
72
|
+
all_terminations.chain_resolvable(@terminated)
|
73
|
+
all_terminations.chain_resolvable(envelope.future) if envelope && envelope.future
|
74
74
|
|
75
75
|
broadcast(true, [:terminated, reason]) # TODO do not end up in Dead Letter Router
|
76
76
|
parent << :remove_child if parent
|
@@ -42,7 +42,7 @@ module Concurrent
|
|
42
42
|
# @option opts [Class] reference a custom descendant of {Reference} to use
|
43
43
|
# @option opts [Array<Array(Behavior::Abstract, Array<Object>)>] behaviour_definition, array of pairs
|
44
44
|
# where each pair is behaviour class and its args, see {Behaviour.basic_behaviour_definition}
|
45
|
-
# @option opts [
|
45
|
+
# @option opts [ResolvableFuture, nil] initialized, if present it'll be set or failed after {Context} initialization
|
46
46
|
# @option opts [Reference, nil] parent **private api** parent of the actor (the one spawning )
|
47
47
|
# @option opts [Proc, nil] logger a proc accepting (level, progname, message = nil, &block) params,
|
48
48
|
# can be used to hook actor instance to any logging system, see {Concurrent::Concern::Logging}
|
@@ -172,7 +172,6 @@ module Concurrent
|
|
172
172
|
allocate_context
|
173
173
|
|
174
174
|
@executor = Type! opts.fetch(:executor, @context.default_executor), Concurrent::AbstractExecutorService
|
175
|
-
raise ArgumentError, 'ImmediateExecutor is not supported' if @executor.is_a? ImmediateExecutor
|
176
175
|
|
177
176
|
@reference = (Child! opts[:reference_class] || @context.default_reference_class, Reference).new self
|
178
177
|
@name = (Type! opts.fetch(:name), String, Symbol).to_s
|
@@ -192,17 +191,17 @@ module Concurrent
|
|
192
191
|
|
193
192
|
@args = opts.fetch(:args, [])
|
194
193
|
@block = block
|
195
|
-
initialized = Type! opts[:initialized],
|
194
|
+
initialized = Type! opts[:initialized], Promises::ResolvableFuture, NilClass
|
196
195
|
|
197
196
|
schedule_execution do
|
198
197
|
begin
|
199
198
|
build_context
|
200
|
-
initialized.
|
199
|
+
initialized.fulfill reference if initialized
|
201
200
|
log DEBUG, 'spawned'
|
202
201
|
rescue => ex
|
203
202
|
log ERROR, ex
|
204
203
|
@first_behaviour.terminate!
|
205
|
-
initialized.
|
204
|
+
initialized.reject ex if initialized
|
206
205
|
end
|
207
206
|
end
|
208
207
|
end
|
@@ -16,7 +16,7 @@ module Concurrent
|
|
16
16
|
|
17
17
|
def initialize(message, future, sender, address)
|
18
18
|
@message = message
|
19
|
-
@future = Type! future,
|
19
|
+
@future = Type! future, Promises::ResolvableFuture, NilClass
|
20
20
|
@sender = Type! sender, Reference, Thread
|
21
21
|
@address = Type! address, Reference
|
22
22
|
end
|
@@ -34,7 +34,7 @@ module Concurrent
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def reject!(error)
|
37
|
-
future.
|
37
|
+
future.reject error unless future.nil?
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
@@ -45,13 +45,13 @@ module Concurrent
|
|
45
45
|
# global_io_executor will block on while asking. It's fine to use it form outside of actors and
|
46
46
|
# global_io_executor.
|
47
47
|
# @param [Object] message
|
48
|
-
# @param [
|
49
|
-
# @return [
|
48
|
+
# @param [Promises::Future] future to be fulfilled be message's processing result
|
49
|
+
# @return [Promises::Future] supplied future
|
50
50
|
# @example
|
51
51
|
# adder = AdHoc.spawn('adder') { -> message { message + 1 } }
|
52
52
|
# adder.ask(1).value # => 2
|
53
53
|
# adder.ask(nil).wait.reason # => #<NoMethodError: undefined method `+' for nil:NilClass>
|
54
|
-
def ask(message, future = Concurrent.
|
54
|
+
def ask(message, future = Concurrent::Promises.resolvable_future)
|
55
55
|
message message, future
|
56
56
|
end
|
57
57
|
|
@@ -63,13 +63,13 @@ module Concurrent
|
|
63
63
|
# global_io_executor will block on while asking. It's fine to use it form outside of actors and
|
64
64
|
# global_io_executor.
|
65
65
|
# @param [Object] message
|
66
|
-
# @param [
|
66
|
+
# @param [Promises::Future] future to be fulfilled be message's processing result
|
67
67
|
# @return [Object] message's processing result
|
68
|
-
# @raise [Exception] future.reason if future is #
|
68
|
+
# @raise [Exception] future.reason if future is #rejected?
|
69
69
|
# @example
|
70
70
|
# adder = AdHoc.spawn('adder') { -> message { message + 1 } }
|
71
71
|
# adder.ask!(1) # => 2
|
72
|
-
def ask!(message, future = Concurrent.
|
72
|
+
def ask!(message, future = Concurrent::Promises.resolvable_future)
|
73
73
|
ask(message, future).value!
|
74
74
|
end
|
75
75
|
|
@@ -80,7 +80,7 @@ module Concurrent
|
|
80
80
|
# behaves as {#tell} when no future and as {#ask} when future
|
81
81
|
def message(message, future = nil)
|
82
82
|
core.on_envelope Envelope.new(message, future, Actor.current || Thread.current, self)
|
83
|
-
return future ? future.
|
83
|
+
return future ? future.with_hidden_resolvable : self
|
84
84
|
end
|
85
85
|
|
86
86
|
# @see AbstractContext#dead_letter_routing
|
@@ -43,9 +43,9 @@ module Concurrent
|
|
43
43
|
envelope_to_redirect = if envelope.future
|
44
44
|
envelope
|
45
45
|
else
|
46
|
-
Envelope.new(envelope.message, Concurrent.future, envelope.sender, envelope.address)
|
46
|
+
Envelope.new(envelope.message, Concurrent::Promises.future, envelope.sender, envelope.address)
|
47
47
|
end
|
48
|
-
envelope_to_redirect.future.
|
48
|
+
envelope_to_redirect.future.on_fulfillment! { @balancer << :subscribe } # TODO check safety of @balancer reading
|
49
49
|
redirect @balancer, envelope_to_redirect
|
50
50
|
end
|
51
51
|
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# Provides tools for cooperative cancellation.
|
4
|
+
# Inspired by https://msdn.microsoft.com/en-us/library/dd537607(v=vs.110).aspx
|
5
|
+
# @example
|
6
|
+
# # Create new cancellation. `cancellation` is used for cancelling, `token` is passed down to
|
7
|
+
# # tasks for cooperative cancellation
|
8
|
+
# cancellation, token = Concurrent::Cancellation.create
|
9
|
+
# Thread.new(token) do |token|
|
10
|
+
# # Count 1+1 (simulating some other meaningful work) repeatedly until the token is cancelled through
|
11
|
+
# # cancellation.
|
12
|
+
# token.loop_until_canceled { 1+1 }
|
13
|
+
# end
|
14
|
+
# sleep 0.1
|
15
|
+
# cancellation.cancel # Stop the thread by cancelling
|
16
|
+
class Cancellation < Synchronization::Object
|
17
|
+
safe_initialization!
|
18
|
+
|
19
|
+
# Creates the cancellation object. Returns both the cancellation and the token for convenience.
|
20
|
+
# @param [Object] resolve_args resolve_args Arguments which are used when resolve method is called on
|
21
|
+
# resolvable_future_or_event
|
22
|
+
# @param [Promises::Resolvable] resolvable_future_or_event resolvable used to track cancellation.
|
23
|
+
# Can be retrieved by `token.to_future` ot `token.to_event`.
|
24
|
+
# @example
|
25
|
+
# cancellation, token = Concurrent::Cancellation.create
|
26
|
+
# @return [Array(Cancellation, Cancellation::Token)]
|
27
|
+
def self.create(resolvable_future_or_event = Promises.resolvable_event, *resolve_args)
|
28
|
+
cancellation = new(resolvable_future_or_event, *resolve_args)
|
29
|
+
[cancellation, cancellation.token]
|
30
|
+
end
|
31
|
+
|
32
|
+
private_class_method :new
|
33
|
+
|
34
|
+
# Returns the token associated with the cancellation.
|
35
|
+
# @return [Token]
|
36
|
+
def token
|
37
|
+
@Token
|
38
|
+
end
|
39
|
+
|
40
|
+
# Cancel this cancellation. All executions depending on the token will cooperatively stop.
|
41
|
+
# @return [true, false]
|
42
|
+
# @raise when cancelling for the second tim
|
43
|
+
def cancel(raise_on_repeated_call = true)
|
44
|
+
!!@Cancel.resolve(*@ResolveArgs, raise_on_repeated_call)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Is the cancellation cancelled?
|
48
|
+
# @return [true, false]
|
49
|
+
def canceled?
|
50
|
+
@Cancel.resolved?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Short string representation.
|
54
|
+
# @return [String]
|
55
|
+
def to_s
|
56
|
+
format '<#%s:0x%x canceled:%s>', self.class, object_id << 1, canceled?
|
57
|
+
end
|
58
|
+
|
59
|
+
alias_method :inspect, :to_s
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def initialize(future, *resolve_args)
|
64
|
+
raise ArgumentError, 'future is not Resolvable' unless future.is_a?(Promises::Resolvable)
|
65
|
+
@Cancel = future
|
66
|
+
@Token = Token.new @Cancel.with_hidden_resolvable
|
67
|
+
@ResolveArgs = resolve_args
|
68
|
+
end
|
69
|
+
|
70
|
+
# Created through {Cancellation.create}, passed down to tasks to be able to check if canceled.
|
71
|
+
class Token < Synchronization::Object
|
72
|
+
safe_initialization!
|
73
|
+
|
74
|
+
# @return [Event] Event which will be resolved when the token is cancelled.
|
75
|
+
def to_event
|
76
|
+
@Cancel.to_event
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Future] Future which will be resolved when the token is cancelled with arguments passed in
|
80
|
+
# {Cancellation.create} .
|
81
|
+
def to_future
|
82
|
+
@Cancel.to_future
|
83
|
+
end
|
84
|
+
|
85
|
+
# Is the token cancelled?
|
86
|
+
# @return [true, false]
|
87
|
+
def canceled?
|
88
|
+
@Cancel.resolved?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Repeatedly evaluates block until the token is {#canceled?}.
|
92
|
+
# @yield to the block repeatedly.
|
93
|
+
# @yieldreturn [Object]
|
94
|
+
# @return [Object] last result of the block
|
95
|
+
def loop_until_canceled(&block)
|
96
|
+
until canceled?
|
97
|
+
result = block.call
|
98
|
+
end
|
99
|
+
result
|
100
|
+
end
|
101
|
+
|
102
|
+
# Raise error when cancelled
|
103
|
+
# @param [#exception] error to be risen
|
104
|
+
# @raise the error
|
105
|
+
# @return [self]
|
106
|
+
def raise_if_canceled(error = CancelledOperationError)
|
107
|
+
raise error if canceled?
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# Creates a new token which is cancelled when any of the tokens is.
|
112
|
+
# @param [Token] tokens to combine
|
113
|
+
# @return [Token] new token
|
114
|
+
def join(*tokens, &block)
|
115
|
+
block ||= -> tokens { Promises.any_event(*tokens.map(&:to_event)) }
|
116
|
+
self.class.new block.call([@Cancel, *tokens])
|
117
|
+
end
|
118
|
+
|
119
|
+
# Short string representation.
|
120
|
+
# @return [String]
|
121
|
+
def to_s
|
122
|
+
format '<#%s:0x%x canceled:%s>', self.class, object_id << 1, canceled?
|
123
|
+
end
|
124
|
+
|
125
|
+
alias_method :inspect, :to_s
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def initialize(cancel)
|
130
|
+
@Cancel = cancel
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# FIXME (pitr-ch 27-Mar-2016): cooperation with mutex, condition, select etc?
|
135
|
+
# TODO (pitr-ch 27-Mar-2016): examples (scheduled to be cancelled in 10 sec)
|
136
|
+
end
|
137
|
+
end
|