concurrent-ruby-edge 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
|
10
10
|
[![Gitter chat](https://img.shields.io/badge/IRC%20(gitter)-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
|