concurrent-ruby-edge 0.3.1 → 0.7.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.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +552 -0
  3. data/LICENSE.txt +18 -18
  4. data/README.md +261 -103
  5. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/abstract.rb +2 -0
  6. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/awaits.rb +2 -0
  7. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/buffer.rb +2 -0
  8. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/errors_on_unknown_message.rb +2 -0
  9. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/executes_context.rb +2 -0
  10. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/linking.rb +2 -0
  11. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/pausing.rb +2 -0
  12. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/removes_child.rb +2 -0
  13. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/sets_results.rb +2 -0
  14. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/supervising.rb +2 -0
  15. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour/termination.rb +3 -1
  16. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/behaviour.rb +1 -1
  17. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/context.rb +3 -1
  18. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/core.rb +5 -4
  19. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/default_dead_letter_handler.rb +2 -0
  20. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/envelope.rb +2 -0
  21. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/errors.rb +2 -0
  22. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/internal_delegations.rb +3 -0
  23. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/reference.rb +9 -8
  24. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/root.rb +3 -0
  25. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/ad_hoc.rb +2 -0
  26. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/balancer.rb +2 -0
  27. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/broadcast.rb +1 -0
  28. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils/pool.rb +1 -0
  29. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor.rb +11 -6
  30. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/base.rb +14 -14
  31. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/dropping.rb +1 -0
  32. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/sliding.rb +1 -0
  33. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/unbuffered.rb +1 -1
  34. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/tick.rb +1 -1
  35. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel.rb +3 -2
  36. data/lib/concurrent-ruby-edge/concurrent/edge/cancellation.rb +107 -0
  37. data/lib/concurrent-ruby-edge/concurrent/edge/channel.rb +453 -0
  38. data/lib/concurrent-ruby-edge/concurrent/edge/erlang_actor.rb +1549 -0
  39. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_linked_set/node.rb +2 -2
  40. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_linked_set.rb +8 -7
  41. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_queue.rb +2 -0
  42. data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/old_channel_integration.rb +2 -0
  43. data/lib/concurrent-ruby-edge/concurrent/edge/processing_actor.rb +184 -0
  44. data/lib/concurrent-ruby-edge/concurrent/edge/promises.rb +174 -0
  45. data/lib/concurrent-ruby-edge/concurrent/edge/throttle.rb +229 -0
  46. data/lib/concurrent-ruby-edge/concurrent/edge/version.rb +3 -0
  47. data/lib/concurrent-ruby-edge/concurrent/edge.rb +21 -0
  48. data/lib/concurrent-ruby-edge/concurrent/executor/wrapping_executor.rb +50 -0
  49. data/lib/concurrent-ruby-edge/concurrent/lazy_register.rb +83 -0
  50. data/lib/{concurrent-edge.rb → concurrent-ruby-edge/concurrent-edge.rb} +5 -4
  51. metadata +71 -67
  52. data/lib/concurrent/edge/atomic_markable_reference.rb +0 -184
  53. data/lib/concurrent/edge/cancellation.rb +0 -138
  54. data/lib/concurrent/edge/lock_free_stack.rb +0 -126
  55. data/lib/concurrent/edge/processing_actor.rb +0 -161
  56. data/lib/concurrent/edge/promises.rb +0 -2111
  57. data/lib/concurrent/edge/throttle.rb +0 -192
  58. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/public_delegations.rb +0 -0
  59. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/type_check.rb +0 -0
  60. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/actor/utils.rb +0 -0
  61. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/buffered.rb +0 -0
  62. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/ticker.rb +0 -0
  63. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer/timer.rb +0 -0
  64. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/buffer.rb +0 -0
  65. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/after_clause.rb +0 -0
  66. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/default_clause.rb +0 -0
  67. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/error_clause.rb +0 -0
  68. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/put_clause.rb +0 -0
  69. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector/take_clause.rb +0 -0
  70. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/channel/selector.rb +0 -0
  71. /data/lib/{concurrent → concurrent-ruby-edge/concurrent}/edge/lock_free_linked_set/window.rb +0 -0
@@ -1,138 +0,0 @@
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
- #
6
- # @example
7
- # # Create new cancellation. `cancellation` is used for cancelling, `token` is passed down to
8
- # # tasks for cooperative cancellation
9
- # cancellation, token = Concurrent::Cancellation.create
10
- # Thread.new(token) do |token|
11
- # # Count 1+1 (simulating some other meaningful work) repeatedly
12
- # # until the token is cancelled through cancellation.
13
- # token.loop_until_canceled { 1+1 }
14
- # end
15
- # sleep 0.1
16
- # cancellation.cancel # Stop the thread by cancelling
17
- class Cancellation < Synchronization::Object
18
- safe_initialization!
19
-
20
- # Creates the cancellation object. Returns both the cancellation and the token for convenience.
21
- # @param [Object] resolve_args resolve_args Arguments which are used when resolve method is called on
22
- # resolvable_future_or_event
23
- # @param [Promises::Resolvable] resolvable_future_or_event resolvable used to track cancellation.
24
- # Can be retrieved by `token.to_future` ot `token.to_event`.
25
- # @example
26
- # cancellation, token = Concurrent::Cancellation.create
27
- # @return [Array(Cancellation, Cancellation::Token)]
28
- def self.create(resolvable_future_or_event = Promises.resolvable_event, *resolve_args)
29
- cancellation = new(resolvable_future_or_event, *resolve_args)
30
- [cancellation, cancellation.token]
31
- end
32
-
33
- private_class_method :new
34
-
35
- # Returns the token associated with the cancellation.
36
- # @return [Token]
37
- def token
38
- @Token
39
- end
40
-
41
- # Cancel this cancellation. All executions depending on the token will cooperatively stop.
42
- # @return [true, false]
43
- # @raise when cancelling for the second tim
44
- def cancel(raise_on_repeated_call = true)
45
- !!@Cancel.resolve(*@ResolveArgs, raise_on_repeated_call)
46
- end
47
-
48
- # Is the cancellation cancelled?
49
- # @return [true, false]
50
- def canceled?
51
- @Cancel.resolved?
52
- end
53
-
54
- # Short string representation.
55
- # @return [String]
56
- def to_s
57
- format '<#%s:0x%x canceled:%s>', self.class, object_id << 1, canceled?
58
- end
59
-
60
- alias_method :inspect, :to_s
61
-
62
- private
63
-
64
- def initialize(future, *resolve_args)
65
- raise ArgumentError, 'future is not Resolvable' unless future.is_a?(Promises::Resolvable)
66
- @Cancel = future
67
- @Token = Token.new @Cancel.with_hidden_resolvable
68
- @ResolveArgs = resolve_args
69
- end
70
-
71
- # Created through {Cancellation.create}, passed down to tasks to be able to check if canceled.
72
- class Token < Synchronization::Object
73
- safe_initialization!
74
-
75
- # @return [Event] Event which will be resolved when the token is cancelled.
76
- def to_event
77
- @Cancel.to_event
78
- end
79
-
80
- # @return [Future] Future which will be resolved when the token is cancelled with arguments passed in
81
- # {Cancellation.create} .
82
- def to_future
83
- @Cancel.to_future
84
- end
85
-
86
- # Is the token cancelled?
87
- # @return [true, false]
88
- def canceled?
89
- @Cancel.resolved?
90
- end
91
-
92
- # Repeatedly evaluates block until the token is {#canceled?}.
93
- # @yield to the block repeatedly.
94
- # @yieldreturn [Object]
95
- # @return [Object] last result of the block
96
- def loop_until_canceled(&block)
97
- until canceled?
98
- result = block.call
99
- end
100
- result
101
- end
102
-
103
- # Raise error when cancelled
104
- # @param [#exception] error to be risen
105
- # @raise the error
106
- # @return [self]
107
- def raise_if_canceled(error = CancelledOperationError)
108
- raise error if canceled?
109
- self
110
- end
111
-
112
- # Creates a new token which is cancelled when any of the tokens is.
113
- # @param [Token] tokens to combine
114
- # @return [Token] new token
115
- def join(*tokens, &block)
116
- block ||= -> tokens { Promises.any_event(*tokens.map(&:to_event)) }
117
- self.class.new block.call([@Cancel, *tokens])
118
- end
119
-
120
- # Short string representation.
121
- # @return [String]
122
- def to_s
123
- format '<#%s:0x%x canceled:%s>', self.class, object_id << 1, canceled?
124
- end
125
-
126
- alias_method :inspect, :to_s
127
-
128
- private
129
-
130
- def initialize(cancel)
131
- @Cancel = cancel
132
- end
133
- end
134
-
135
- # FIXME (pitr-ch 27-Mar-2016): cooperation with mutex, condition, select etc?
136
- # TODO (pitr-ch 27-Mar-2016): examples (scheduled to be cancelled in 10 sec)
137
- end
138
- end
@@ -1,126 +0,0 @@
1
- module Concurrent
2
-
3
- # @!visibility private
4
- class LockFreeStack < Synchronization::Object
5
-
6
- safe_initialization!
7
-
8
- class Node
9
- # TODO (pitr-ch 20-Dec-2016): Could be unified with Stack class?
10
-
11
- attr_reader :value, :next_node
12
- # allow to nil-ify to free GC when the entry is no longer relevant, not synchronised
13
- attr_writer :value
14
-
15
- def initialize(value, next_node)
16
- @value = value
17
- @next_node = next_node
18
- end
19
-
20
- singleton_class.send :alias_method, :[], :new
21
- end
22
-
23
- class Empty < Node
24
- def next_node
25
- self
26
- end
27
- end
28
-
29
- EMPTY = Empty[nil, nil]
30
-
31
- private(*attr_atomic(:head))
32
-
33
- def self.of1(value)
34
- new Node[value, EMPTY]
35
- end
36
-
37
- def self.of2(value1, value2)
38
- new Node[value1, Node[value2, EMPTY]]
39
- end
40
-
41
- def initialize(head = EMPTY)
42
- super()
43
- self.head = head
44
- end
45
-
46
- def empty?(head = self.head)
47
- head.equal? EMPTY
48
- end
49
-
50
- def compare_and_push(head, value)
51
- compare_and_set_head head, Node[value, head]
52
- end
53
-
54
- def push(value)
55
- while true
56
- current_head = head
57
- return self if compare_and_set_head current_head, Node[value, current_head]
58
- end
59
- end
60
-
61
- def peek
62
- head
63
- end
64
-
65
- def compare_and_pop(head)
66
- compare_and_set_head head, head.next_node
67
- end
68
-
69
- def pop
70
- while true
71
- current_head = head
72
- return current_head.value if compare_and_set_head current_head, current_head.next_node
73
- end
74
- end
75
-
76
- def compare_and_clear(head)
77
- compare_and_set_head head, EMPTY
78
- end
79
-
80
- include Enumerable
81
-
82
- def each(head = nil)
83
- return to_enum(:each, head) unless block_given?
84
- it = head || peek
85
- until it.equal?(EMPTY)
86
- yield it.value
87
- it = it.next_node
88
- end
89
- self
90
- end
91
-
92
- def clear
93
- while true
94
- current_head = head
95
- return false if current_head == EMPTY
96
- return true if compare_and_set_head current_head, EMPTY
97
- end
98
- end
99
-
100
- def clear_if(head)
101
- compare_and_set_head head, EMPTY
102
- end
103
-
104
- def replace_if(head, new_head)
105
- compare_and_set_head head, new_head
106
- end
107
-
108
- def clear_each(&block)
109
- while true
110
- current_head = head
111
- return self if current_head == EMPTY
112
- if compare_and_set_head current_head, EMPTY
113
- each current_head, &block
114
- return self
115
- end
116
- end
117
- end
118
-
119
- # @return [String] Short string representation.
120
- def to_s
121
- format '<#%s:0x%x %s>', self.class, object_id << 1, to_a.to_s
122
- end
123
-
124
- alias_method :inspect, :to_s
125
- end
126
- end
@@ -1,161 +0,0 @@
1
- module Concurrent
2
-
3
- # A new implementation of actor which also simulates the process, therefore it can be used
4
- # in the same way as Erlang's actors but **without** occupying thread. A tens of thousands
5
- # ProcessingActors can run at the same time sharing a thread pool.
6
- # @example
7
- # # Runs on a pool, does not consume 50_000 threads
8
- # actors = 50_000.times.map do |i|
9
- # Concurrent::ProcessingActor.act(i) { |a, i| a.receive.then_on(:fast, i) { |m, i| m + i } }
10
- # end
11
- #
12
- # actors.each { |a| a.tell 1 }
13
- # values = actors.map(&:termination).map(&:value)
14
- # values[0,5] # => [1, 2, 3, 4, 5]
15
- # values[-5, 5] # => [49996, 49997, 49998, 49999, 50000]
16
- # @!macro edge_warning
17
- class ProcessingActor < Synchronization::Object
18
- # TODO (pitr-ch 18-Dec-2016): (un)linking, bidirectional, sends special message, multiple link calls has no effect,
19
- # TODO (pitr-ch 21-Dec-2016): Make terminated a cancellation token?
20
- # link_spawn atomic, Can it be fixed by sending exit when linked dead actor?
21
-
22
- safe_initialization!
23
-
24
- # @return [Promises::Channel] actor's mailbox.
25
- def mailbox
26
- @Mailbox
27
- end
28
-
29
- # @return [Promises::Future(Object)] a future which is resolved when the actor ends its processing.
30
- # It can either be fulfilled with a value when actor ends normally or rejected with
31
- # a reason (exception) when actor fails.
32
- def termination
33
- @Terminated.with_hidden_resolvable
34
- end
35
-
36
- # Creates an actor.
37
- # @see #act_listening Behaves the same way, but does not take mailbox as a first argument.
38
- # @return [ProcessingActor]
39
- # @example
40
- # actor = Concurrent::ProcessingActor.act do |actor|
41
- # actor.receive.then do |message|
42
- # # the actor ends normally with message
43
- # message
44
- # end
45
- # end
46
- #
47
- # actor.tell :a_message
48
- # # => <#Concurrent::ProcessingActor:0x7fff11280560 termination:pending>
49
- # actor.termination.value! # => :a_message
50
- def self.act(*args, &process)
51
- act_listening Promises::Channel.new, *args, &process
52
- end
53
-
54
- # Creates an actor listening to a specified channel (mailbox).
55
- # @param [Object] args Arguments passed to the process.
56
- # @param [Promises::Channel] channel which serves as mailing box. The channel can have limited
57
- # size to achieve backpressure.
58
- # @yield args to the process to get back a future which represents the actors execution.
59
- # @yieldparam [Object] *args
60
- # @yieldreturn [Promises::Future(Object)] a future representing next step of execution
61
- # @return [ProcessingActor]
62
- # @example
63
- # # TODO (pitr-ch 19-Jan-2017): actor with limited mailbox
64
- def self.act_listening(channel, *args, &process)
65
- actor = ProcessingActor.new channel
66
- Promises.
67
- future(actor, *args, &process).
68
- run.
69
- chain_resolvable(actor.instance_variable_get(:@Terminated))
70
- actor
71
- end
72
-
73
- # Receives a message when available, used in the actor's process.
74
- # @return [Promises::Future(Object)] a future which will be fulfilled with a message from
75
- # mailbox when it is available.
76
- def receive(probe = Promises.resolvable_future)
77
- # TODO (pitr-ch 27-Dec-2016): patterns
78
- @Mailbox.pop probe
79
- end
80
-
81
- # Tells a message to the actor. May block current thread if the mailbox is full.
82
- # {#tell} is a better option since it does not block. It's usually used to integrate with
83
- # threading code.
84
- # @example
85
- # Thread.new(actor) do |actor|
86
- # # ...
87
- # actor.tell! :a_message # blocks until the message is told
88
- # # (there is a space for it in the channel)
89
- # # ...
90
- # end
91
- # @param [Object] message
92
- # @return [self]
93
- def tell!(message)
94
- @Mailbox.push(message).wait!
95
- self
96
- end
97
-
98
- # Tells a message to the actor.
99
- # @param [Object] message
100
- # @return [Promises::Future(ProcessingActor)] a future which will be fulfilled with the actor
101
- # when the message is pushed to mailbox.
102
- def tell(message)
103
- @Mailbox.push(message).then(self) { |_, actor| actor }
104
- end
105
-
106
- # Simplifies common pattern when a message sender also requires an answer to the message
107
- # from the actor. It appends a resolvable_future for the answer after the message.
108
- # @todo has to be nice also on the receive side, cannot make structure like this [message = [...], answer]
109
- # all receives should receive something friendly
110
- # @param [Object] message
111
- # @param [Promises::ResolvableFuture] answer
112
- # @return [Promises::Future] a future which will be fulfilled with the answer to the message
113
- # @example
114
- # add_once_actor = Concurrent::ProcessingActor.act do |actor|
115
- # actor.receive.then do |(a, b), answer|
116
- # result = a + b
117
- # answer.fulfill result
118
- # # terminate with result value
119
- # result
120
- # end
121
- # end
122
- # # => <#Concurrent::ProcessingActor:0x7fcd1315f6e8 termination:pending>
123
- #
124
- # add_once_actor.ask([1, 2]).value! # => 3
125
- # # fails the actor already added once
126
- # add_once_actor.ask(%w(ab cd)).reason
127
- # # => #<RuntimeError: actor terminated normally before answering with a value: 3>
128
- # add_once_actor.termination.value! # => 3
129
- def ask(message, answer = Promises.resolvable_future)
130
- tell [message, answer]
131
- # do not leave answers unanswered when actor terminates.
132
- Promises.any(
133
- Promises.fulfilled_future(:answer).zip(answer),
134
- Promises.fulfilled_future(:termination).zip(@Terminated)
135
- ).chain do |fulfilled, (which, value), (_, reason)|
136
- # TODO (pitr-ch 20-Jan-2017): we have to know which future was resolved
137
- # TODO (pitr-ch 20-Jan-2017): make the combinator programmable, so anyone can create what is needed
138
- # TODO (pitr-ch 19-Jan-2017): ensure no callbacks are accumulated on @Terminated
139
- if which == :termination
140
- raise reason.nil? ? format('actor terminated normally before answering with a value: %s', value) : reason
141
- else
142
- fulfilled ? value : raise(reason)
143
- end
144
- end
145
- end
146
-
147
- # @return [String] string representation.
148
- def inspect
149
- format '<#%s:0x%x termination:%s>', self.class, object_id << 1, termination.state
150
- end
151
-
152
- private
153
-
154
- def initialize(channel = Promises::Channel.new)
155
- @Mailbox = channel
156
- @Terminated = Promises.resolvable_future
157
- super()
158
- end
159
-
160
- end
161
- end