concurrent-ruby-edge 0.1.0.pre2

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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +284 -0
  4. data/lib/concurrent-edge.rb +11 -0
  5. data/lib/concurrent/actor.rb +98 -0
  6. data/lib/concurrent/actor/behaviour.rb +143 -0
  7. data/lib/concurrent/actor/behaviour/abstract.rb +51 -0
  8. data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
  9. data/lib/concurrent/actor/behaviour/buffer.rb +56 -0
  10. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
  11. data/lib/concurrent/actor/behaviour/executes_context.rb +17 -0
  12. data/lib/concurrent/actor/behaviour/linking.rb +83 -0
  13. data/lib/concurrent/actor/behaviour/pausing.rb +123 -0
  14. data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
  15. data/lib/concurrent/actor/behaviour/sets_results.rb +37 -0
  16. data/lib/concurrent/actor/behaviour/supervising.rb +39 -0
  17. data/lib/concurrent/actor/behaviour/terminates_children.rb +14 -0
  18. data/lib/concurrent/actor/behaviour/termination.rb +74 -0
  19. data/lib/concurrent/actor/context.rb +167 -0
  20. data/lib/concurrent/actor/core.rb +220 -0
  21. data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
  22. data/lib/concurrent/actor/envelope.rb +41 -0
  23. data/lib/concurrent/actor/errors.rb +27 -0
  24. data/lib/concurrent/actor/internal_delegations.rb +59 -0
  25. data/lib/concurrent/actor/public_delegations.rb +40 -0
  26. data/lib/concurrent/actor/reference.rb +106 -0
  27. data/lib/concurrent/actor/root.rb +37 -0
  28. data/lib/concurrent/actor/type_check.rb +48 -0
  29. data/lib/concurrent/actor/utils.rb +10 -0
  30. data/lib/concurrent/actor/utils/ad_hoc.rb +27 -0
  31. data/lib/concurrent/actor/utils/balancer.rb +43 -0
  32. data/lib/concurrent/actor/utils/broadcast.rb +52 -0
  33. data/lib/concurrent/actor/utils/pool.rb +54 -0
  34. data/lib/concurrent/agent.rb +289 -0
  35. data/lib/concurrent/channel.rb +6 -0
  36. data/lib/concurrent/channel/blocking_ring_buffer.rb +82 -0
  37. data/lib/concurrent/channel/buffered_channel.rb +87 -0
  38. data/lib/concurrent/channel/channel.rb +19 -0
  39. data/lib/concurrent/channel/ring_buffer.rb +65 -0
  40. data/lib/concurrent/channel/unbuffered_channel.rb +39 -0
  41. data/lib/concurrent/channel/waitable_list.rb +48 -0
  42. data/lib/concurrent/edge/atomic_markable_reference.rb +184 -0
  43. data/lib/concurrent/edge/future.rb +1226 -0
  44. data/lib/concurrent/edge/lock_free_stack.rb +85 -0
  45. metadata +110 -0
@@ -0,0 +1,9 @@
1
+ module Concurrent
2
+ module Actor
3
+ class DefaultDeadLetterHandler < RestartingContext
4
+ def on_message(dead_letter)
5
+ log INFO, "got dead letter #{dead_letter.inspect}"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,41 @@
1
+ module Concurrent
2
+ module Actor
3
+ class Envelope
4
+ include TypeCheck
5
+
6
+ # @!attribute [r] message
7
+ # @return [Object] a message
8
+ # @!attribute [r] future
9
+ # @return [Edge::Future] a future which becomes resolved after message is processed
10
+ # @!attribute [r] sender
11
+ # @return [Reference, Thread] an actor or thread sending the message
12
+ # @!attribute [r] address
13
+ # @return [Reference] where this message will be delivered
14
+
15
+ attr_reader :message, :future, :sender, :address
16
+
17
+ def initialize(message, future, sender, address)
18
+ @message = message
19
+ @future = Type! future, Edge::CompletableFuture, NilClass
20
+ @sender = Type! sender, Reference, Thread
21
+ @address = Type! address, Reference
22
+ end
23
+
24
+ def sender_path
25
+ if sender.is_a? Reference
26
+ sender.path
27
+ else
28
+ sender.to_s
29
+ end
30
+ end
31
+
32
+ def address_path
33
+ address.path
34
+ end
35
+
36
+ def reject!(error)
37
+ future.fail error unless future.nil?
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ module Concurrent
2
+ module Actor
3
+ Error = Class.new(StandardError)
4
+
5
+ class ActorTerminated < Error
6
+ include TypeCheck
7
+
8
+ attr_reader :reference
9
+
10
+ def initialize(reference)
11
+ @reference = Type! reference, Reference
12
+ super reference.path
13
+ end
14
+ end
15
+
16
+ class UnknownMessage < Error
17
+ include TypeCheck
18
+
19
+ attr_reader :envelope
20
+
21
+ def initialize(envelope)
22
+ @envelope = Type! envelope, Envelope
23
+ super envelope.message.inspect
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,59 @@
1
+ module Concurrent
2
+ module Actor
3
+ module InternalDelegations
4
+ include PublicDelegations
5
+
6
+ # @see Core#children
7
+ def children
8
+ core.children
9
+ end
10
+
11
+ # @see Termination#terminate!
12
+ def terminate!(reason = nil)
13
+ behaviour!(Behaviour::Termination).terminate!(reason)
14
+ end
15
+
16
+ # @see Termination#terminated?
17
+ def terminated?
18
+ behaviour!(Behaviour::Termination).terminated?
19
+ end
20
+
21
+ # @see Termination#reason
22
+ def reason
23
+ behaviour!(Behaviour::Termination).reason
24
+ end
25
+
26
+ # delegates to core.log
27
+ # @see Logging#log
28
+ def log(level, message = nil, &block)
29
+ core.log(level, message, &block)
30
+ end
31
+
32
+ # @see AbstractContext#dead_letter_routing
33
+ def dead_letter_routing
34
+ context.dead_letter_routing
35
+ end
36
+
37
+ def redirect(reference, envelope = self.envelope)
38
+ reference.message(envelope.message, envelope.future)
39
+ Behaviour::MESSAGE_PROCESSED
40
+ end
41
+
42
+ # @return [AbstractContext]
43
+ def context
44
+ core.context
45
+ end
46
+
47
+ # see Core#behaviour
48
+ def behaviour(behaviour_class)
49
+ core.behaviour(behaviour_class)
50
+ end
51
+
52
+ # see Core#behaviour!
53
+ def behaviour!(behaviour_class)
54
+ core.behaviour!(behaviour_class)
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,40 @@
1
+ module Concurrent
2
+ module Actor
3
+
4
+ # Provides publicly expose-able methods from {Core}.
5
+ module PublicDelegations
6
+ # @see Core#name
7
+ def name
8
+ core.name
9
+ end
10
+
11
+ # @see Core#path
12
+ def path
13
+ core.path
14
+ end
15
+
16
+ # @see Core#parent
17
+ def parent
18
+ core.parent
19
+ end
20
+
21
+ # @see Core#reference
22
+ def reference
23
+ core.reference
24
+ end
25
+
26
+ # @see Core#executor
27
+ def executor
28
+ core.executor
29
+ end
30
+
31
+ # @see Core#context_class
32
+ def context_class
33
+ core.context_class
34
+ end
35
+
36
+ alias_method :ref, :reference
37
+ alias_method :actor_class, :context_class
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,106 @@
1
+ module Concurrent
2
+ module Actor
3
+
4
+ # {Reference} is public interface of Actor instances. It is used for sending messages and can
5
+ # be freely passed around the application. It also provides some basic information about the actor,
6
+ # see {PublicDelegations}.
7
+ #
8
+ # AdHoc.spawn('printer') { -> message { puts message } }
9
+ # # => #<Concurrent::Actor::Reference:0x7fd0d2883218 /printer (Concurrent::Actor::Utils::AdHoc)>
10
+ # # ^object_id ^path ^context class
11
+ class Reference
12
+ include TypeCheck
13
+ include PublicDelegations
14
+
15
+ attr_reader :core
16
+ private :core
17
+
18
+ # @!visibility private
19
+ def initialize(core)
20
+ @core = Type! core, Core
21
+ end
22
+
23
+ # Sends the message asynchronously to the actor and immediately returns
24
+ # `self` (the reference) allowing to chain message telling.
25
+ # @param [Object] message
26
+ # @return [Reference] self
27
+ # @example
28
+ # printer = AdHoc.spawn('printer') { -> message { puts message } }
29
+ # printer.tell('ping').tell('pong')
30
+ # printer << 'ping' << 'pong'
31
+ # # => 'ping'\n'pong'\n'ping'\n'pong'\n
32
+ def tell(message)
33
+ message message, nil
34
+ end
35
+
36
+ alias_method :<<, :tell
37
+
38
+ # @note it's a good practice to use tell whenever possible. Ask should be used only for
39
+ # testing and when it returns very shortly. It can lead to deadlock if all threads in
40
+ # global_io_executor will block on while asking. It's fine to use it form outside of actors and
41
+ # global_io_executor.
42
+ #
43
+ # @note it's a good practice to use {#tell} whenever possible. Results can be send back with other messages.
44
+ # Ask should be used only for testing and when it returns very shortly. It can lead to deadlock if all threads in
45
+ # global_io_executor will block on while asking. It's fine to use it form outside of actors and
46
+ # global_io_executor.
47
+ # @param [Object] message
48
+ # @param [Edge::Future] future to be fulfilled be message's processing result
49
+ # @return [Edge::Future] supplied future
50
+ # @example
51
+ # adder = AdHoc.spawn('adder') { -> message { message + 1 } }
52
+ # adder.ask(1).value # => 2
53
+ # adder.ask(nil).wait.reason # => #<NoMethodError: undefined method `+' for nil:NilClass>
54
+ def ask(message, future = Concurrent.future)
55
+ message message, future
56
+ end
57
+
58
+ # Sends the message synchronously and blocks until the message
59
+ # is processed. Raises on error.
60
+ #
61
+ # @note it's a good practice to use {#tell} whenever possible. Results can be send back with other messages.
62
+ # Ask should be used only for testing and when it returns very shortly. It can lead to deadlock if all threads in
63
+ # global_io_executor will block on while asking. It's fine to use it form outside of actors and
64
+ # global_io_executor.
65
+ # @param [Object] message
66
+ # @param [Edge::Future] future to be fulfilled be message's processing result
67
+ # @return [Object] message's processing result
68
+ # @raise [Exception] future.reason if future is #failed?
69
+ # @example
70
+ # adder = AdHoc.spawn('adder') { -> message { message + 1 } }
71
+ # adder.ask!(1) # => 2
72
+ def ask!(message, future = Concurrent.future)
73
+ ask(message, future).value!
74
+ end
75
+
76
+ def map(messages)
77
+ messages.map { |m| self.ask(m) }
78
+ end
79
+
80
+ # behaves as {#tell} when no future and as {#ask} when future
81
+ def message(message, future = nil)
82
+ core.on_envelope Envelope.new(message, future, Actor.current || Thread.current, self)
83
+ return future ? future.hide_completable : self
84
+ end
85
+
86
+ # @see AbstractContext#dead_letter_routing
87
+ def dead_letter_routing
88
+ core.dead_letter_routing
89
+ end
90
+
91
+ def to_s
92
+ "#<#{self.class}:0x#{'%x' % (object_id << 1)} #{path} (#{actor_class})>"
93
+ end
94
+
95
+ alias_method :inspect, :to_s
96
+
97
+ def ==(other)
98
+ Type? other, self.class and other.send(:core) == core
99
+ end
100
+
101
+ # to avoid confusion with Kernel.spawn
102
+ undef_method :spawn
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,37 @@
1
+ module Concurrent
2
+ module Actor
3
+ # implements the root actor
4
+ class Root < AbstractContext
5
+
6
+ def initialize
7
+ # noinspection RubyArgCount
8
+ @dead_letter_router = Core.new(parent: reference,
9
+ class: DefaultDeadLetterHandler,
10
+ supervise: true,
11
+ name: :default_dead_letter_handler).reference
12
+ end
13
+
14
+ # to allow spawning of new actors, spawn needs to be called inside the parent Actor
15
+ def on_message(message)
16
+ case
17
+ when message.is_a?(Array) && message.first == :spawn
18
+ Actor.spawn message[1], &message[2]
19
+ when message == :dead_letter_routing
20
+ @dead_letter_router
21
+ else
22
+ # ignore
23
+ end
24
+ end
25
+
26
+ def dead_letter_routing
27
+ @dead_letter_router
28
+ end
29
+
30
+ def behaviour_definition
31
+ [*Behaviour.base(:just_log),
32
+ *Behaviour.supervising,
33
+ *Behaviour.user_messages]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,48 @@
1
+ module Concurrent
2
+ module Actor
3
+
4
+ # taken from Algebrick
5
+ # supplies type-checking helpers whenever included
6
+ module TypeCheck
7
+
8
+ def Type?(value, *types)
9
+ types.any? { |t| value.is_a? t }
10
+ end
11
+
12
+ def Type!(value, *types)
13
+ Type?(value, *types) or
14
+ TypeCheck.error(value, 'is not', types)
15
+ value
16
+ end
17
+
18
+ def Match?(value, *types)
19
+ types.any? { |t| t === value }
20
+ end
21
+
22
+ def Match!(value, *types)
23
+ Match?(value, *types) or
24
+ TypeCheck.error(value, 'is not matching', types)
25
+ value
26
+ end
27
+
28
+ def Child?(value, *types)
29
+ Type?(value, Class) &&
30
+ types.any? { |t| value <= t }
31
+ end
32
+
33
+ def Child!(value, *types)
34
+ Child?(value, *types) or
35
+ TypeCheck.error(value, 'is not child', types)
36
+ value
37
+ end
38
+
39
+ private
40
+
41
+ def self.error(value, message, types)
42
+ raise TypeError,
43
+ "Value (#{value.class}) '#{value}' #{message} any of: #{types.join('; ')}."
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,10 @@
1
+ module Concurrent
2
+ module Actor
3
+ module Utils
4
+ require 'concurrent/actor/utils/ad_hoc'
5
+ require 'concurrent/actor/utils/broadcast'
6
+ require 'concurrent/actor/utils/balancer'
7
+ require 'concurrent/actor/utils/pool'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ module Concurrent
2
+ module Actor
3
+ module Utils
4
+
5
+ module AsAdHoc
6
+ def initialize(*args, &initializer)
7
+ @on_message = Type! initializer.call(*args), Proc
8
+ end
9
+
10
+ def on_message(message)
11
+ instance_exec message, &@on_message
12
+ end
13
+ end
14
+
15
+ # Allows quick creation of actors with behaviour defined by blocks.
16
+ # @example ping
17
+ # AdHoc.spawn :forward, an_actor do |where|
18
+ # # this block has to return proc defining #on_message behaviour
19
+ # -> message { where.tell message }
20
+ # end
21
+ # @note TODO remove in favor of the module
22
+ class AdHoc < Context
23
+ include AsAdHoc
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,43 @@
1
+ module Concurrent
2
+ module Actor
3
+ module Utils
4
+
5
+ # Distributes messages between subscribed actors. Each actor'll get only one message then
6
+ # it's unsubscribed. The actor needs to resubscribe when it's ready to receive next message.
7
+ # It will buffer the messages if there is no worker registered.
8
+ # @see Pool
9
+ class Balancer < RestartingContext
10
+
11
+ def initialize
12
+ @receivers = []
13
+ @buffer = []
14
+ end
15
+
16
+ def on_message(message)
17
+ command, who = message
18
+ case command
19
+ when :subscribe
20
+ @receivers << (who || envelope.sender)
21
+ distribute
22
+ true
23
+ when :unsubscribe
24
+ @receivers.delete(who || envelope.sender)
25
+ true
26
+ when :subscribed?
27
+ @receivers.include?(who || envelope.sender)
28
+ else
29
+ @buffer << envelope
30
+ distribute
31
+ Behaviour::MESSAGE_PROCESSED
32
+ end
33
+ end
34
+
35
+ def distribute
36
+ while !@receivers.empty? && !@buffer.empty?
37
+ redirect @receivers.shift, @buffer.shift
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end