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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a73af2e066aeacd850f4d90e0b6e479e49f01c2
4
- data.tar.gz: 5579712d4b29cea99935106ede50cc838e879dcb
3
+ metadata.gz: 051e8d02906d153d112b5f2e2c86948d3cf0fd26
4
+ data.tar.gz: f4a53c06cfbf931dfc11245be8dabdf579d0e361
5
5
  SHA512:
6
- metadata.gz: a35e94c5897426ea016c966f29bf18c0a06c46f90365e45fa82d69584c9f3c2680f95d9920354fc17211b9cced7475cca596d6c3a116a1b8a153ded45a5ce720
7
- data.tar.gz: b8a8f94e251a406dde85ec6003de8cfc8e83bdadafff888244e28201666d386b7a549d2da697dd5973c79c152d43fc06de4a86a512bb717465e537cbff222ded
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
- <table>
13
- <tr>
14
- <td align="left" valign="top">
15
- <p>
16
- Modern concurrency tools for Ruby. Inspired by
17
- <a href="http://www.erlang.org/doc/reference_manual/processes.html">Erlang</a>,
18
- <a href="http://clojure.org/concurrent_programming">Clojure</a>,
19
- <a href="http://akka.io/">Scala</a>,
20
- <a href="http://www.haskell.org/haskellwiki/Applications_and_libraries/Concurrency_and_parallelism#Concurrent_Haskell">Haskell</a>,
21
- <a href="http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx">F#</a>,
22
- <a href="http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx">C#</a>,
23
- <a href="http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html">Java</a>,
24
- and classic concurrency patterns.
25
- </p>
26
- <p>
27
- The design goals of this gem are:
28
- <ul>
29
- <li>Be an 'unopinionated' toolbox that provides useful utilities without debating which is better or why</li>
30
- <li>Remain free of external gem dependencies</li>
31
- <li>Stay true to the spirit of the languages providing inspiration</li>
32
- <li>But implement in a way that makes sense for Ruby</li>
33
- <li>Keep the semantics as idiomatic Ruby as possible</li>
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
- * [Actor](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor.html):
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/Edge/LockFreeStack.html)
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
 
@@ -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'
@@ -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/future'
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.future).reference.tap do
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(to_spawn_options(*args).merge(parent: Actor.current), &block).reference
73
+ Core.new(options.merge(parent: Actor.current), &block).reference
70
74
  else
71
- root.ask([:spawn, to_spawn_options(*args), block]).value!
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.future), &block).tap { future.wait! }
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 CompletableFuture in the {Envelope} or error on failure.
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.success result
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.fail error unless envelope.future.nil?
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.future
18
- @public_terminated = @terminated.hide_completable
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.completed?
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.completed_future(reason.nil?, reason.nil? || nil, reason)
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.chain_completable(@terminated)
73
- all_terminations.chain_completable(envelope.future) if envelope && envelope.future
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 [CompletableFuture, nil] initialized, if present it'll be set or failed after {Context} initialization
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], Edge::CompletableFuture, NilClass
194
+ initialized = Type! opts[:initialized], Promises::ResolvableFuture, NilClass
196
195
 
197
196
  schedule_execution do
198
197
  begin
199
198
  build_context
200
- initialized.success reference if 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.fail ex if 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, Edge::CompletableFuture, NilClass
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.fail error unless future.nil?
37
+ future.reject error unless future.nil?
38
38
  end
39
39
  end
40
40
  end
@@ -20,7 +20,7 @@ module Concurrent
20
20
 
21
21
  def initialize(envelope)
22
22
  @envelope = Type! envelope, Envelope
23
- super envelope.message.inspect
23
+ super "#{envelope.message.inspect} from #{envelope.sender_path}"
24
24
  end
25
25
  end
26
26
  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 [Edge::Future] future to be fulfilled be message's processing result
49
- # @return [Edge::Future] supplied future
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.future)
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 [Edge::Future] future to be fulfilled be message's processing result
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 #failed?
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.future)
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.hide_completable : self
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.on_completion! { @balancer << :subscribe } # TODO check safety of @balancer reading
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