concurrent-ruby-edge 0.3.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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