rflow 1.3.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.yardopts +1 -1
- data/Vagrantfile +14 -10
- data/lib/rflow.rb +16 -1
- data/lib/rflow/broker.rb +6 -2
- data/lib/rflow/child_process.rb +23 -3
- data/lib/rflow/component.rb +68 -18
- data/lib/rflow/component/port.rb +111 -14
- data/lib/rflow/components/clock.rb +51 -0
- data/lib/rflow/components/integer.rb +40 -2
- data/lib/rflow/components/log.rb +39 -0
- data/lib/rflow/components/raw.rb +27 -0
- data/lib/rflow/components/replicate.rb +19 -0
- data/lib/rflow/components/ruby_proc_filter.rb +24 -0
- data/lib/rflow/configuration.rb +63 -25
- data/lib/rflow/configuration/component.rb +15 -0
- data/lib/rflow/configuration/connection.rb +39 -8
- data/lib/rflow/configuration/port.rb +20 -1
- data/lib/rflow/configuration/setting.rb +6 -0
- data/lib/rflow/configuration/shard.rb +7 -1
- data/lib/rflow/configuration/uuid_keyed.rb +5 -0
- data/lib/rflow/connection.rb +46 -11
- data/lib/rflow/connections/zmq_connection.rb +18 -1
- data/lib/rflow/daemon_process.rb +25 -0
- data/lib/rflow/logger.rb +29 -0
- data/lib/rflow/master.rb +15 -0
- data/lib/rflow/message.rb +69 -12
- data/lib/rflow/pid_file.rb +13 -2
- data/lib/rflow/shard.rb +37 -6
- data/lib/rflow/version.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a854e3c0cf56feae660eb061c29d9cbaad028a34
|
4
|
+
data.tar.gz: ac21f337ed623cb7a0a96c8612dc17f5e1e63bd2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d580e43a06b900a9cfcb8cd7372d933b698f5dc28c40909a1a2ed4211c342af497829f44e3699b520499dfd7007e70fc6925c88b8354a63278d6402cd504f5bc
|
7
|
+
data.tar.gz: b5d50e712d85913f0052e3a3c3cd5d2c73a4e69280af1cc0d3f92aa1fcc8917181db8a7d5288b1cab17787161543945023f6b170a47cc9d883428197d35173a0
|
data/.gitignore
CHANGED
data/.yardopts
CHANGED
@@ -1 +1 @@
|
|
1
|
-
--output ./doc --main README.md --files schema/*.avsc lib/**/*.rb bin/*.rb - README.md LICENSE
|
1
|
+
--output ./doc --main README.md --files schema/*.avsc lib/**/*.rb bin/*.rb --exclude lib/rflow/configuration/migrations --exclude lib/rflow/configuration/ruby_dsl.rb - README.md LICENSE
|
data/Vagrantfile
CHANGED
@@ -3,14 +3,17 @@
|
|
3
3
|
VAGRANTFILE_API_VERSION = '2'
|
4
4
|
|
5
5
|
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
6
|
-
config.vm.define 'centos62' do |c|
|
7
|
-
c.vm.box = 'jstoneham/rflow-centos62'
|
8
|
-
end
|
9
|
-
config.vm.define 'centos64' do |c|
|
10
|
-
c.vm.box = 'box-cutter/centos64'
|
11
|
-
end
|
12
|
-
config.vm.define 'centos65' do |c|
|
13
|
-
c.vm.box = 'chef/centos-6.5'
|
6
|
+
# config.vm.define 'centos62' do |c|
|
7
|
+
# c.vm.box = 'jstoneham/rflow-centos62'
|
8
|
+
# end
|
9
|
+
# config.vm.define 'centos64' do |c|
|
10
|
+
# c.vm.box = 'box-cutter/centos64'
|
11
|
+
# end
|
12
|
+
# config.vm.define 'centos65' do |c|
|
13
|
+
# c.vm.box = 'chef/centos-6.5'
|
14
|
+
# end
|
15
|
+
config.vm.define 'centos67' do |c|
|
16
|
+
c.vm.box = 'boxcutter/centos67'
|
14
17
|
end
|
15
18
|
|
16
19
|
config.vm.synced_folder '.', '/rflow'
|
@@ -25,14 +28,14 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|
25
28
|
|
26
29
|
# install RPM dependencies for rflow and zeromq
|
27
30
|
config.vm.provision 'shell', privileged: true, inline: <<-EOS
|
28
|
-
curl -
|
31
|
+
curl -OL https://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
|
29
32
|
rpm -ivh epel-release-6-8.noarch.rpm
|
30
33
|
yum -y install libyaml-devel patch libffi-devel glibc-headers autoconf gcc-c++ glibc-devel readline-devel zlib-devel openssl-devel automake libtool bison git sqlite-devel rpm-build libuuid-devel vim
|
31
34
|
EOS
|
32
35
|
|
33
36
|
# build zeromq as vagrant user
|
34
37
|
config.vm.provision 'shell', privileged: false, inline: <<-EOS
|
35
|
-
curl -
|
38
|
+
curl -OL https://archive.org/download/zeromq_3.2.4/zeromq-3.2.4.tar.gz
|
36
39
|
rpmbuild -tb zeromq-3.2.4.tar.gz
|
37
40
|
EOS
|
38
41
|
|
@@ -44,6 +47,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|
44
47
|
# set up RVM and bundler
|
45
48
|
config.vm.provision 'shell', privileged: false, inline: <<-EOS
|
46
49
|
rm -f .profile
|
50
|
+
gpg2 --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
|
47
51
|
curl -sSL https://get.rvm.io | bash -s stable
|
48
52
|
source .rvm/scripts/rvm
|
49
53
|
rvm install `cat /rflow/.ruby-version`
|
data/lib/rflow.rb
CHANGED
@@ -11,16 +11,31 @@ require 'rflow/components'
|
|
11
11
|
require 'rflow/connections'
|
12
12
|
require 'rflow/logger'
|
13
13
|
|
14
|
+
# The RFlow application.
|
14
15
|
class RFlow
|
15
16
|
include Log4r
|
16
17
|
|
17
18
|
class << self
|
19
|
+
# RFlow's logger, whose message will be routed to logs as specified in the configuration.
|
20
|
+
# @return [RFlow::Logger]
|
18
21
|
attr_accessor :logger
|
19
|
-
|
22
|
+
|
23
|
+
# RFlow's configuration.
|
24
|
+
# @return [RFlow::Configuration]
|
25
|
+
attr_reader :configuration
|
26
|
+
|
27
|
+
# RFlow's master node which oversees all the others.
|
28
|
+
# @return [RFlow::Master]
|
29
|
+
attr_reader :master
|
20
30
|
|
21
31
|
RFlow.logger = RFlow::Logger.new({})
|
22
32
|
end
|
23
33
|
|
34
|
+
# Start RFlow running. This is the main programmatic entry point to the application.
|
35
|
+
# Pulls in the configuration, sets up logging, and starts the master note.
|
36
|
+
#
|
37
|
+
# @param config_database_path [String] the path to the SQLite configuration database
|
38
|
+
# @param daemonize [boolean] true to fork and daemonize; false to run in the foreground
|
24
39
|
def self.run!(config_database_path = nil, daemonize = false)
|
25
40
|
@config_database_path = config_database_path
|
26
41
|
@daemonize = daemonize
|
data/lib/rflow/broker.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
require 'rflow/child_process'
|
2
2
|
|
3
3
|
class RFlow
|
4
|
-
# A message broker to mediate messages along a connection.
|
5
|
-
# The broker runs in a child process and will not return from spawn
|
4
|
+
# A message broker process to mediate messages along a connection.
|
5
|
+
# The broker runs in a child process and will not return from {spawn!}.
|
6
6
|
class Broker < ChildProcess
|
7
7
|
class << self
|
8
|
+
# Build the broker from the connection configuration.
|
9
|
+
# Only supports {RFlow::Configuration::ZMQStreamer} configurations.
|
10
|
+
# @param config [RFlow::Configuration::ZMQStreamer] the connection configuration
|
11
|
+
# @return [RFlow::Connections::ZMQStreamer]
|
8
12
|
def build(config)
|
9
13
|
case config.class.name
|
10
14
|
when 'RFlow::Configuration::ZMQStreamer'
|
data/lib/rflow/child_process.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
1
|
require 'fcntl'
|
2
2
|
|
3
3
|
class RFlow
|
4
|
+
# Encapsulates a child process being managed by RFlow.
|
4
5
|
class ChildProcess
|
5
|
-
|
6
|
-
|
6
|
+
# The PID of the child process.
|
7
|
+
# @return [Fixnum]
|
8
|
+
attr_reader :pid
|
9
|
+
# The name of the child process.
|
10
|
+
# @return [String]
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# Symbolic constant for SIGINFO as this is only defined on BSD and not in Ruby.
|
7
14
|
SIGINFO = 29
|
8
15
|
|
16
|
+
# @param name [String] process name
|
17
|
+
# @param role [String] role to be played by the process, for logging (Master, Broker, etc.)
|
9
18
|
def initialize(name, role = name)
|
10
19
|
@name = name
|
11
20
|
@role = role
|
@@ -13,7 +22,12 @@ class RFlow
|
|
13
22
|
|
14
23
|
# Launch another process to execute the child. The parent
|
15
24
|
# process retains the original worker object (with pid and IPC
|
16
|
-
# pipe) to allow for process management
|
25
|
+
# pipe) to allow for process management. Parent will
|
26
|
+
# return once the child starts; child will update its process
|
27
|
+
# name, detach from the process group, set up signal handlers, and
|
28
|
+
# execute {run_child_process}; when that returns, it will
|
29
|
+
# exit with return code 0.
|
30
|
+
# @return [void]
|
17
31
|
def spawn!
|
18
32
|
establish_child_pipe
|
19
33
|
drop_database_connections
|
@@ -26,6 +40,7 @@ class RFlow
|
|
26
40
|
end
|
27
41
|
end
|
28
42
|
|
43
|
+
protected
|
29
44
|
def run_child_process
|
30
45
|
@child_pipe_w.close
|
31
46
|
register_logging_context
|
@@ -42,6 +57,11 @@ class RFlow
|
|
42
57
|
|
43
58
|
def run_process; end
|
44
59
|
|
60
|
+
public
|
61
|
+
# Called when the child process needs to be shut down, before it dies.
|
62
|
+
# Clears signal handlers.
|
63
|
+
# @param signal [String] SIG*, whichever signal caused the shutdown
|
64
|
+
# @return [void]
|
45
65
|
def shutdown!(signal)
|
46
66
|
RFlow.logger.info "Shutting down due to #{signal}"
|
47
67
|
unhandle_signals
|
data/lib/rflow/component.rb
CHANGED
@@ -3,22 +3,37 @@ require 'rflow/message'
|
|
3
3
|
require 'rflow/component/port'
|
4
4
|
|
5
5
|
class RFlow
|
6
|
+
# Parent class for all RFlow components.
|
6
7
|
class Component
|
7
8
|
class << self
|
8
|
-
# Keep track of available component subclasses
|
9
|
+
# Keep track of available component subclasses.
|
10
|
+
# @!visibility private
|
9
11
|
def inherited(subclass)
|
10
12
|
RFlow::Configuration.add_available_component(subclass)
|
11
13
|
end
|
12
14
|
|
13
|
-
#
|
15
|
+
# When declaring your component class, defines an input port with a given
|
16
|
+
# name. Will also define a port accessor method named after the port for
|
17
|
+
# retrieving it.
|
18
|
+
#
|
19
|
+
# @param name [String]
|
20
|
+
# @return [void]
|
14
21
|
def input_port(name); define_port(defined_input_ports, name); end
|
15
22
|
|
16
|
-
#
|
23
|
+
# When declaring your component class, defines an output port with a
|
24
|
+
# given name. Will also define a port accessor method named after the
|
25
|
+
# port for retrieving it.
|
26
|
+
#
|
27
|
+
# @param name [String]
|
28
|
+
# @return [void]
|
17
29
|
def output_port(name); define_port(defined_output_ports, name); end
|
18
30
|
|
31
|
+
# @!visibility private
|
19
32
|
def defined_input_ports; @defined_input_ports ||= {}; end
|
33
|
+
# @!visibility private
|
20
34
|
def defined_output_ports; @defined_output_ports ||= {}; end
|
21
35
|
|
36
|
+
# @!visibility private
|
22
37
|
def define_port(collection, name)
|
23
38
|
collection[name.to_s] = true
|
24
39
|
|
@@ -32,10 +47,14 @@ class RFlow
|
|
32
47
|
# specification. This assumes that the specification of a
|
33
48
|
# component is a fully qualified Ruby class that has already
|
34
49
|
# been loaded. It will first attempt to find subclasses of
|
35
|
-
# RFlow::Component (in
|
50
|
+
# {RFlow::Component} (in {Configuration#available_components}) and then
|
36
51
|
# attempt to constantize the specification into a different
|
37
52
|
# class. Future releases will support external (i.e. non-managed
|
38
|
-
# components), but the current stuff only supports Ruby classes
|
53
|
+
# components), but the current stuff only supports Ruby classes.
|
54
|
+
#
|
55
|
+
# @param worker [Shard::Worker] the worker process for the component to run in
|
56
|
+
# @param config [Configuration::Component] the component configuration
|
57
|
+
# @return [RFlow::Component] an instance of the component class
|
39
58
|
def build(worker, config)
|
40
59
|
raise NotImplementedError, "Non-managed components not yet implemented for component '#{config.name}' as '#{config.specification}' (#{config.uuid})" unless config.managed?
|
41
60
|
|
@@ -74,9 +93,20 @@ class RFlow
|
|
74
93
|
end
|
75
94
|
end
|
76
95
|
|
77
|
-
|
78
|
-
|
79
|
-
|
96
|
+
# The UUID of the component.
|
97
|
+
# @return [String]
|
98
|
+
attr_accessor :uuid
|
99
|
+
# The name of the component.
|
100
|
+
# @return [String]
|
101
|
+
attr_accessor :name
|
102
|
+
# Collection of the component's input and output ports.
|
103
|
+
# @return [PortCollection]
|
104
|
+
attr_reader :ports
|
105
|
+
# Reference to the worker process in which this instance of the component is running.
|
106
|
+
# @return [Shard::Worker]
|
107
|
+
attr_reader :worker
|
108
|
+
|
109
|
+
# @param args [Hash] supported args are +:name+, +:uuid+, +:worker+
|
80
110
|
def initialize(args = {})
|
81
111
|
@name = args[:name]
|
82
112
|
@uuid = args[:uuid]
|
@@ -87,16 +117,22 @@ class RFlow
|
|
87
117
|
self.class.defined_output_ports.each {|name, _| ports << OutputPort.new(self, name: name) }
|
88
118
|
end
|
89
119
|
|
120
|
+
# @!attribute shard [r]
|
121
|
+
# Reference to the component's worker process's {Shard}.
|
122
|
+
# @return [Shard]
|
90
123
|
def shard; worker.shard if worker; end
|
91
124
|
|
92
125
|
# Returns a list of connected input ports. Each port will have
|
93
126
|
# one or more keys associated with a particular connection.
|
127
|
+
# @return [Array<InputPort>]
|
94
128
|
def input_ports; ports.by_type['RFlow::Component::InputPort']; end
|
95
129
|
|
96
130
|
# Returns a list of connected output ports. Each port will have
|
97
131
|
# one or more keys associated with the particular connection.
|
132
|
+
# @return [Array<OutputPort>]
|
98
133
|
def output_ports; ports.by_type['RFlow::Component::OutputPort']; end
|
99
134
|
|
135
|
+
# @!visibility private
|
100
136
|
def configure_input_port!(port_name, options = {})
|
101
137
|
RFlow.logger.debug "Configuring component '#{name}' (#{uuid}) input port '#{port_name}' (#{options[:uuid]})"
|
102
138
|
unless self.class.defined_input_ports.include? port_name
|
@@ -105,6 +141,7 @@ class RFlow
|
|
105
141
|
ports.by_name[port_name].uuid = options[:uuid]
|
106
142
|
end
|
107
143
|
|
144
|
+
# @!visibility private
|
108
145
|
def configure_output_port!(port_name, options = {})
|
109
146
|
RFlow.logger.debug "Configuring component '#{name}' (#{uuid}) output port '#{port_name}' (#{options[:uuid]})"
|
110
147
|
unless self.class.defined_output_ports.include? port_name
|
@@ -113,6 +150,7 @@ class RFlow
|
|
113
150
|
ports.by_name[port_name].uuid = options[:uuid]
|
114
151
|
end
|
115
152
|
|
153
|
+
# @!visibility private
|
116
154
|
# Tell the component to establish its ports' connections, i.e. make
|
117
155
|
# the connection. Uses the underlying connection object. Also
|
118
156
|
# establishes the callbacks for each of the input ports
|
@@ -121,12 +159,16 @@ class RFlow
|
|
121
159
|
input_ports.each(&:connect!)
|
122
160
|
end
|
123
161
|
|
162
|
+
# @!visibility private
|
124
163
|
# Tell the component to establish its ports' connections, i.e. make
|
125
164
|
# the connection. Uses the underlying connection object.
|
165
|
+
# @!visibility private
|
126
166
|
def connect_outputs!
|
127
167
|
output_ports.each(&:connect!)
|
128
168
|
end
|
129
169
|
|
170
|
+
# Pretty-printed version of the component, its ports, their keys, and their connections.
|
171
|
+
# @return [String]
|
130
172
|
def to_s
|
131
173
|
string = "Component '#{name}' (#{uuid})\n"
|
132
174
|
ports.each do |port|
|
@@ -141,29 +183,37 @@ class RFlow
|
|
141
183
|
|
142
184
|
# Method that should be overridden by a subclass to provide for
|
143
185
|
# component-specific configuration. The subclass should use the
|
144
|
-
#
|
145
|
-
# particular configuration.
|
146
|
-
#
|
147
|
-
#
|
186
|
+
# {configuration} attribute (+@configuration+) to store its
|
187
|
+
# particular configuration.
|
188
|
+
# @param deserialized_configuration [Hash] from the RFlow configuration database; most likely a Hash. Don't assume that the keys are symbols!
|
189
|
+
# @return [void]
|
148
190
|
def configure!(deserialized_configuration); end
|
149
191
|
|
150
192
|
# Main component running method. Subclasses should implement if
|
151
193
|
# they want to set up any EventMachine stuffs (servers, clients,
|
152
|
-
# etc)
|
194
|
+
# etc.).
|
195
|
+
# @return [void]
|
153
196
|
def run!; end
|
154
197
|
|
155
198
|
# Method called when a message is received on an input port.
|
156
|
-
# Subclasses should implement if they want to receive messages
|
199
|
+
# Subclasses should implement if they want to receive messages.
|
200
|
+
# @param input_port [RFlow::Component::InputPort] the input port the message was received on
|
201
|
+
# @param input_port_key [String] if the message was received on a keyed subport, this is the key
|
202
|
+
# @param connection [RFlow::Connection] the connection the message was received on
|
203
|
+
# @param message [RFlow::Message] the message itself
|
204
|
+
# @return [void]
|
157
205
|
def process_message(input_port, input_port_key, connection, message); end
|
158
206
|
|
159
207
|
# Method called when RFlow is shutting down. Subclasses should
|
160
|
-
#
|
161
|
-
# and stop sending new data through the flow
|
208
|
+
# implement to terminate any servers/clients (or let them finish)
|
209
|
+
# and stop sending new data through the flow.
|
210
|
+
# @return [void]
|
162
211
|
def shutdown!; end
|
163
212
|
|
164
213
|
# Method called after all components have been shutdown! and just
|
165
|
-
# before the global RFlow exit.
|
166
|
-
# cleanup any leftover state, e.g. flush file handles, etc
|
214
|
+
# before the global RFlow exit. Sublcasses should implement to
|
215
|
+
# cleanup any leftover state, e.g. flush file handles, etc.
|
216
|
+
# @return [void]
|
167
217
|
def cleanup!; end
|
168
218
|
end
|
169
219
|
end
|
data/lib/rflow/component/port.rb
CHANGED
@@ -3,7 +3,9 @@ class RFlow
|
|
3
3
|
# TODO: make this into a class to limit the amount of extensions
|
4
4
|
# that we have to do when operating on these 'Arrays', i.e. when
|
5
5
|
# adding two together
|
6
|
+
# @!visibility private
|
6
7
|
module ConnectionCollection
|
8
|
+
# @!visibility private
|
7
9
|
def send_message(message)
|
8
10
|
each {|connection| connection.send_message(message) }
|
9
11
|
end
|
@@ -12,7 +14,15 @@ class RFlow
|
|
12
14
|
# Collection class to make it easier to index by both names
|
13
15
|
# and types.
|
14
16
|
class PortCollection
|
15
|
-
|
17
|
+
# All the ports in the collection.
|
18
|
+
# @return [Array<Port>]
|
19
|
+
attr_reader :ports
|
20
|
+
# All the ports in the collection, indexed by name.
|
21
|
+
# @return [Hash<String, Port>]
|
22
|
+
attr_reader :by_name
|
23
|
+
# All the ports in the collection, indexed by type ({InputPort}, {OutputPort}).
|
24
|
+
# @return [Hash<String, Array<Port>>]
|
25
|
+
attr_reader :by_type
|
16
26
|
|
17
27
|
def initialize
|
18
28
|
@ports = []
|
@@ -20,6 +30,9 @@ class RFlow
|
|
20
30
|
@by_type = Hash.new {|hash, key| hash[key.to_s] = []}
|
21
31
|
end
|
22
32
|
|
33
|
+
# Add a port to the collection.
|
34
|
+
# @param port [Port] port to add
|
35
|
+
# @return [PortCollection] self
|
23
36
|
def <<(port)
|
24
37
|
by_name[port.name.to_s] = port
|
25
38
|
by_type[port.class.to_s] << port
|
@@ -27,42 +40,65 @@ class RFlow
|
|
27
40
|
self
|
28
41
|
end
|
29
42
|
|
30
|
-
# Enumerate through each port
|
43
|
+
# Enumerate through each port, +yield+ing each.
|
31
44
|
# TODO: simplify with enumerators and procs
|
45
|
+
# @return [Array<Port>]
|
32
46
|
def each
|
33
47
|
ports.each {|port| yield port }
|
34
48
|
end
|
35
49
|
end
|
36
50
|
|
51
|
+
# An input or output port on a {Component}.
|
37
52
|
class Port
|
38
|
-
|
53
|
+
# True if there are connections to the port.
|
54
|
+
# @return [boolean]
|
55
|
+
attr_reader :connected
|
56
|
+
# The {Component} this port belongs to.
|
57
|
+
# @return [Component]
|
58
|
+
attr_reader :component
|
39
59
|
|
40
60
|
def initialize(component)
|
41
61
|
@component = component
|
42
62
|
end
|
43
63
|
|
64
|
+
# Synonym for {connected}.
|
65
|
+
# @return [boolean]
|
44
66
|
def connected?; connected; end
|
45
67
|
end
|
46
68
|
|
47
|
-
#
|
69
|
+
# Represents a keyed subport on a {Component} - that is, an input or output port
|
70
|
+
# that has been subscripted with a port name for subdividing the messages being
|
71
|
+
# received or output.
|
48
72
|
class HashSubPort
|
73
|
+
# @param hash_port [HashPort] the port to which this subport belongs
|
74
|
+
# @param key [String] the key subscript
|
49
75
|
def initialize(hash_port, key)
|
50
76
|
@hash_port = hash_port
|
51
77
|
@key = key
|
52
78
|
end
|
53
79
|
|
80
|
+
# Send a {Message} down all the connections to this subport.
|
81
|
+
# @param message [Message]
|
82
|
+
# @return [void]
|
54
83
|
def send_message(message)
|
55
84
|
connections.each {|connection| connection.send_message(message) }
|
56
85
|
end
|
57
86
|
|
87
|
+
# Retrieve all the connections for this subport.
|
88
|
+
# @return [Array<Connection>]
|
58
89
|
def connections
|
59
90
|
@hash_port.connections_for(@key)
|
60
91
|
end
|
61
92
|
|
93
|
+
# Directly connect this subport to another port.
|
94
|
+
# @param other_port [Port] the other port to connect to
|
95
|
+
# @return [void]
|
62
96
|
def direct_connect(other_port)
|
63
97
|
@hash_port.direct_connect(@key, other_port)
|
64
98
|
end
|
65
99
|
|
100
|
+
# Enumerate the connections to this subport, +yield+ing each.
|
101
|
+
# @return [Array<Connection>]
|
66
102
|
def each
|
67
103
|
connections.each {|connection| yield connection }
|
68
104
|
end
|
@@ -75,9 +111,16 @@ class RFlow
|
|
75
111
|
# result in the same message being sent to all indexed
|
76
112
|
# connections.
|
77
113
|
class HashPort < Port
|
78
|
-
|
114
|
+
# The name of the port.
|
115
|
+
# @return [String]
|
116
|
+
attr_accessor :name
|
117
|
+
# The UUID of the port.
|
118
|
+
# @return [String]
|
119
|
+
attr_accessor :uuid
|
79
120
|
|
80
121
|
public
|
122
|
+
# @param component [Component] the component the port belongs to
|
123
|
+
# @param args [Hash] supported args are +:uuid+ and +:name+
|
81
124
|
def initialize(component, args = {})
|
82
125
|
super(component)
|
83
126
|
self.uuid = args[:uuid]
|
@@ -86,17 +129,21 @@ class RFlow
|
|
86
129
|
end
|
87
130
|
|
88
131
|
# Get the subport for a given key, which can be used to send messages
|
89
|
-
# or direct connection
|
132
|
+
# or direct connection.
|
133
|
+
# @param key [String] the key to subscript with
|
134
|
+
# @return [HashSubPort]
|
90
135
|
def [](key)
|
91
136
|
HashSubPort.new(self, key)
|
92
137
|
end
|
93
138
|
|
94
|
-
# Returns
|
95
|
-
# be sent/received on this subport. Merges the nil
|
139
|
+
# Returns all the connections that should
|
140
|
+
# be sent/received on this subport. Merges the +nil+-keyed port
|
96
141
|
# (i.e. any connections for a port without a key) to those
|
97
142
|
# specific for the key, so should only be used to read a list of
|
98
|
-
# connections, not to add new ones. Use add_connection to add a
|
143
|
+
# connections, not to add new ones. Use {add_connection} to add a
|
99
144
|
# new connection for a given key.
|
145
|
+
# @param key [String] the key to subscript with
|
146
|
+
# @return [Array<Connection>]
|
100
147
|
def connections_for(key)
|
101
148
|
case key
|
102
149
|
when nil; @connections_for[nil]
|
@@ -104,20 +151,32 @@ class RFlow
|
|
104
151
|
end
|
105
152
|
end
|
106
153
|
|
107
|
-
# Adds a connection for a given key
|
154
|
+
# Adds a connection for a given key.
|
155
|
+
# @param key [String] the key to subscript with
|
156
|
+
# @param connection [Connection] the connection to add
|
157
|
+
# @return [void]
|
108
158
|
def add_connection(key, connection)
|
109
159
|
RFlow.logger.debug "Attaching #{connection.class.name} connection '#{connection.name}' (#{connection.uuid}) to port '#{name}' (#{uuid}), key '#{connection.input_port_key}'"
|
110
160
|
@connections_for[key] << connection
|
111
161
|
@all_connections = nil
|
112
162
|
end
|
113
163
|
|
114
|
-
# Removes a connection from a given key
|
164
|
+
# Removes a connection from a given key.
|
165
|
+
# @param key [String] the key to subscript with
|
166
|
+
# @param connection [Connection] the connection to remove
|
167
|
+
# @return [void]
|
115
168
|
def remove_connection(key, connection)
|
116
169
|
RFlow.logger.debug "Removing #{connection.class.name} connection '#{connection.name}' (#{connection.uuid}) from port '#{name}' (#{uuid}), key '#{connection.input_port_key}'"
|
117
170
|
@connections_for[key].delete(connection)
|
118
171
|
@all_connections = nil
|
119
172
|
end
|
120
173
|
|
174
|
+
# Collect messages being sent to this port in a {MessageCollectingConnection}
|
175
|
+
# for retrieval later, usually for unit testing purposes. +yield+s after
|
176
|
+
# establishing the new connection.
|
177
|
+
# @param key [String] the key to subscript with
|
178
|
+
# @param receiver [Array] array in which to place arriving messages
|
179
|
+
# @return [MessageCollectingConnection]
|
121
180
|
def collect_messages(key, receiver)
|
122
181
|
begin
|
123
182
|
connection = RFlow::MessageCollectingConnection.new.tap do |c|
|
@@ -132,6 +191,12 @@ class RFlow
|
|
132
191
|
end
|
133
192
|
end
|
134
193
|
|
194
|
+
# Directly connect this port to another port. If it's an input port,
|
195
|
+
# forward messages to that input port; if it's an output port,
|
196
|
+
# forward messages so they appear to come from that output port.
|
197
|
+
# @param key [String] the key to subscript with
|
198
|
+
# @param other_port [Port] the port to forward to
|
199
|
+
# @return [void]
|
135
200
|
def direct_connect(key = nil, other_port)
|
136
201
|
case other_port
|
137
202
|
when InputPort; add_connection key, ForwardToInputPort.new(other_port)
|
@@ -140,39 +205,62 @@ class RFlow
|
|
140
205
|
end
|
141
206
|
end
|
142
207
|
|
143
|
-
#
|
208
|
+
# A list of connected keys.
|
209
|
+
# @return [Array<String>]
|
144
210
|
def keys
|
145
211
|
@connections_for.keys
|
146
212
|
end
|
147
213
|
|
214
|
+
# Enumerate all connections, +yield+ing each.
|
215
|
+
# @return [Array<Connection>]
|
148
216
|
def each
|
149
217
|
@connections_for.values.each {|connections| yield connections }
|
150
218
|
end
|
151
219
|
|
220
|
+
# Override in subclasses to actually send messages places.
|
221
|
+
# @param message [Message] the message to send
|
222
|
+
# @return [void]
|
152
223
|
def send_message(message)
|
153
224
|
raise NotImplementedError, 'Raw ports do not know how to send messages'
|
154
225
|
end
|
155
226
|
|
156
|
-
#
|
157
|
-
#
|
227
|
+
# Override in subclasses to handle establishing the connection.
|
228
|
+
# @return [void]
|
158
229
|
def connect!; raise NotImplementedError, 'Raw ports do not know which direction to connect'; end
|
159
230
|
|
231
|
+
# Retrieve all connections to the port, regardless of key. The resulting +Array+
|
232
|
+
# also supports +send_message(message)+ which will deliver the message on all
|
233
|
+
# connections.
|
234
|
+
# @return [Array<Connection>]
|
160
235
|
def all_connections
|
161
236
|
@all_connections ||= @connections_for.values.flatten.uniq.extend(ConnectionCollection)
|
162
237
|
end
|
163
238
|
end
|
164
239
|
|
240
|
+
# An actual {Component} input port.
|
165
241
|
class InputPort < HashPort
|
242
|
+
# Connect all the input connections, once everything's been set up.
|
243
|
+
# @return [void]
|
166
244
|
def connect!
|
167
245
|
@connections_for.each {|key, conns| conns.each {|c| c.connect_input! } }
|
168
246
|
@connected = true
|
169
247
|
end
|
170
248
|
|
249
|
+
# Add and start up a new {Connection}.
|
250
|
+
# @param key [String] the key to subscript with
|
251
|
+
# @param connection [Connection] the connection to add
|
252
|
+
# @return [void]
|
171
253
|
def add_connection(key, connection)
|
172
254
|
super
|
173
255
|
connection.connect_input! if connected?
|
174
256
|
end
|
175
257
|
|
258
|
+
# Once things have been set up, registering the receive callback
|
259
|
+
# will set it on all connections, so that when messages are received,
|
260
|
+
# they are delivered on all connections with appropriate key and connection
|
261
|
+
# information from the context of the connection.
|
262
|
+
# @param callback [Proc] the receive callback
|
263
|
+
# @return [void]
|
176
264
|
def recv_callback=(callback)
|
177
265
|
@connections_for.each do |key, connections|
|
178
266
|
connections.each do |connection|
|
@@ -184,12 +272,19 @@ class RFlow
|
|
184
272
|
end
|
185
273
|
end
|
186
274
|
|
275
|
+
# An actual {Component} output port.
|
187
276
|
class OutputPort < HashPort
|
277
|
+
# Connect all the output connections, once everything's been set up.
|
278
|
+
# @return [void]
|
188
279
|
def connect!
|
189
280
|
@connections_for.each {|key, conns| conns.each {|c| c.connect_output! } }
|
190
281
|
@connected = true
|
191
282
|
end
|
192
283
|
|
284
|
+
# Add and start up a new {Connection}.
|
285
|
+
# @param key [String] the key to subscript with
|
286
|
+
# @param connection [Connection] the connection to add
|
287
|
+
# @return [void]
|
193
288
|
def add_connection(key, connection)
|
194
289
|
super
|
195
290
|
connection.connect_output! if connected?
|
@@ -197,6 +292,8 @@ class RFlow
|
|
197
292
|
|
198
293
|
# Send a message to all connections on all keys for this port,
|
199
294
|
# but only once per connection.
|
295
|
+
# @param message [RFlow::Message] the message to send
|
296
|
+
# @return [void]
|
200
297
|
def send_message(message)
|
201
298
|
all_connections.send_message(message)
|
202
299
|
end
|