rflow 1.3.0 → 1.3.1
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.
- 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
|