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
@@ -3,14 +3,29 @@ require 'rflow/configuration/uuid_keyed'
|
|
3
3
|
|
4
4
|
class RFlow
|
5
5
|
class Configuration
|
6
|
+
# Represents a component definition in the SQLite database.
|
6
7
|
class Component < ConfigurationItem
|
7
8
|
include UUIDKeyed
|
8
9
|
include ActiveModel::Validations
|
9
10
|
|
11
|
+
# @!attribute options
|
12
|
+
# Open-ended Hash of component options, serialized via YAML to a single column.
|
13
|
+
# @return [Hash]
|
10
14
|
serialize :options, Hash
|
11
15
|
|
16
|
+
# @!attribute shard
|
17
|
+
# The {Shard} in which this {Component} is to run.
|
18
|
+
# @return [Shard]
|
12
19
|
belongs_to :shard, :primary_key => 'uuid', :foreign_key => 'shard_uuid'
|
20
|
+
|
21
|
+
# @!attribute input_ports
|
22
|
+
# The {InputPort}s of this component.
|
23
|
+
# @return [Array<InputPort>]
|
13
24
|
has_many :input_ports, :primary_key => 'uuid', :foreign_key => 'component_uuid'
|
25
|
+
|
26
|
+
# @!attribute output_ports
|
27
|
+
# The {OutputPort}s of this component.
|
28
|
+
# @return [Array<OutputPort>]
|
14
29
|
has_many :output_ports, :primary_key => 'uuid', :foreign_key => 'component_uuid'
|
15
30
|
|
16
31
|
#TODO: Get this to work
|
@@ -3,15 +3,27 @@ require 'rflow/configuration/uuid_keyed'
|
|
3
3
|
|
4
4
|
class RFlow
|
5
5
|
class Configuration
|
6
|
+
# Represents a component-to-component connection in the SQLite database.
|
6
7
|
class Connection < ConfigurationItem
|
8
|
+
# Exception for when the connection is invalid.
|
7
9
|
class ConnectionInvalid < StandardError; end
|
8
10
|
|
9
11
|
include UUIDKeyed
|
10
12
|
include ActiveModel::Validations
|
11
13
|
|
14
|
+
# @!attribute options
|
15
|
+
# Open-ended Hash of component options, serialized via YAML to a single column.
|
16
|
+
# @return [Hash]
|
12
17
|
serialize :options, Hash
|
13
18
|
|
19
|
+
# @!attribute input_port
|
20
|
+
# The {InputPort} to which this {Connection} delivers messages.
|
21
|
+
# @return [InputPort]
|
14
22
|
belongs_to :input_port, :primary_key => 'uuid', :foreign_key => 'input_port_uuid'
|
23
|
+
|
24
|
+
# @!attribute output_port
|
25
|
+
# The {OutputPort} from which this {Connection} receives messages.
|
26
|
+
# @return [OutputPort]
|
15
27
|
belongs_to :output_port,:primary_key => 'uuid', :foreign_key => 'output_port_uuid'
|
16
28
|
|
17
29
|
before_create :merge_default_options!
|
@@ -21,6 +33,7 @@ class RFlow
|
|
21
33
|
|
22
34
|
validate :all_required_options_present?
|
23
35
|
|
36
|
+
# @!visibility private
|
24
37
|
def all_required_options_present?
|
25
38
|
self.class.required_options.each do |option_name|
|
26
39
|
unless self.options.include? option_name.to_s
|
@@ -29,6 +42,7 @@ class RFlow
|
|
29
42
|
end
|
30
43
|
end
|
31
44
|
|
45
|
+
# @!visibility private
|
32
46
|
def merge_default_options!
|
33
47
|
self.options ||= {}
|
34
48
|
self.class.default_options.each do |name, default_value_or_proc|
|
@@ -37,22 +51,29 @@ class RFlow
|
|
37
51
|
end
|
38
52
|
|
39
53
|
# Should return a list of require option names which will be
|
40
|
-
# used in validations.
|
54
|
+
# used in validations. To be overridden by subclasses.
|
55
|
+
# @return [Array<String>]
|
41
56
|
def self.required_options; []; end
|
42
57
|
|
43
58
|
# Should return a hash of default options, where the keys are
|
44
59
|
# the option names and the values are either default option
|
45
60
|
# values or Procs that take a single connection argument. This
|
46
61
|
# allow defaults to use other parameters in the connection to
|
47
|
-
# construct the appropriate default value.
|
62
|
+
# construct the appropriate default value. To be overridden
|
63
|
+
# by subclasses.
|
64
|
+
# @return [Hash]
|
48
65
|
def self.default_options; {}; end
|
49
66
|
|
50
67
|
# By default, no broker processes are required to manage a connection.
|
68
|
+
# To be overridden by subclasses.
|
69
|
+
# @return [Array<Broker>]
|
51
70
|
def brokers; []; end
|
52
71
|
end
|
53
72
|
|
54
|
-
#
|
73
|
+
# Subclass of {Connection} for ZMQ connections and their required options.
|
55
74
|
class ZMQConnection < Connection
|
75
|
+
# Default options required for ZeroMQ connection.
|
76
|
+
# @return [Hash]
|
56
77
|
def self.default_options
|
57
78
|
{
|
58
79
|
'output_socket_type' => 'PUSH',
|
@@ -65,15 +86,16 @@ class RFlow
|
|
65
86
|
end
|
66
87
|
end
|
67
88
|
|
68
|
-
#
|
89
|
+
# Subclass of {Connection} for brokered ZMQ connections and their required options.
|
69
90
|
#
|
70
91
|
# We name the IPCs to resemble a quasi-component. Outputting to this
|
71
|
-
# connection goes to the
|
72
|
-
# connection comes from the
|
92
|
+
# connection goes to the +in+ of the IPC pair. Reading input from this
|
93
|
+
# connection comes from the +out+ of the IPC pair.
|
73
94
|
#
|
74
95
|
# The broker shuttles messages between the two to support the many-to-many
|
75
96
|
# delivery pattern.
|
76
97
|
class BrokeredZMQConnection < Connection
|
98
|
+
# Default ZeroMQ options required for broker connection.
|
77
99
|
def self.default_options
|
78
100
|
{
|
79
101
|
'output_socket_type' => 'PUSH',
|
@@ -86,6 +108,7 @@ class RFlow
|
|
86
108
|
end
|
87
109
|
|
88
110
|
# A brokered ZMQ connection requires one broker process.
|
111
|
+
# @return [Array<Broker>]
|
89
112
|
def brokers
|
90
113
|
@brokers ||= [ZMQStreamer.new(self)]
|
91
114
|
end
|
@@ -95,6 +118,8 @@ class RFlow
|
|
95
118
|
# that can't be derived from the connection. Not persisted in the database -
|
96
119
|
# it's encapsulated in the nature of the connection.
|
97
120
|
class ZMQStreamer
|
121
|
+
# Backreference to the {Connection}.
|
122
|
+
# @return [Connection]
|
98
123
|
attr_reader :connection
|
99
124
|
|
100
125
|
def initialize(connection)
|
@@ -102,9 +127,15 @@ class RFlow
|
|
102
127
|
end
|
103
128
|
end
|
104
129
|
|
105
|
-
#
|
130
|
+
# For testing purposes only.
|
131
|
+
# @!visibility private
|
106
132
|
class NullConnectionConfiguration
|
107
|
-
attr_accessor :name
|
133
|
+
attr_accessor :name
|
134
|
+
attr_accessor :uuid
|
135
|
+
attr_accessor :options
|
136
|
+
attr_accessor :input_port_key
|
137
|
+
attr_accessor :output_port_key
|
138
|
+
attr_accessor :delivery
|
108
139
|
end
|
109
140
|
end
|
110
141
|
end
|
@@ -3,24 +3,43 @@ require 'rflow/configuration/uuid_keyed'
|
|
3
3
|
|
4
4
|
class RFlow
|
5
5
|
class Configuration
|
6
|
+
# Represents a component port in the SQLite database.
|
6
7
|
class Port < ConfigurationItem
|
7
8
|
include UUIDKeyed
|
8
9
|
include ActiveModel::Validations
|
9
10
|
|
11
|
+
# @!attribute component
|
12
|
+
# The {Component} to which this port belongs.
|
13
|
+
# @return [Component]
|
10
14
|
belongs_to :component, :primary_key => 'uuid', :foreign_key => 'component_uuid'
|
11
15
|
|
12
16
|
# TODO: Make some sort of component/port validation work here
|
13
17
|
#validate :component_has_defined_port
|
14
18
|
end
|
15
19
|
|
16
|
-
#
|
20
|
+
# Subclass of {Port} to represent input ports.
|
17
21
|
class InputPort < Port
|
22
|
+
# @!attribute input_connections
|
23
|
+
# The connections delivering messages to this {InputPort}.
|
24
|
+
# @return [Array<Connection>]
|
18
25
|
has_many :input_connections, :class_name => 'RFlow::Configuration::Connection', :primary_key => 'uuid', :foreign_key => 'input_port_uuid'
|
26
|
+
|
27
|
+
# @!attribute connections
|
28
|
+
# Synonym for {input_connections}.
|
29
|
+
# @return [Array<Connection>]
|
19
30
|
has_many :connections, :class_name => 'RFlow::Configuration::Connection', :primary_key => 'uuid', :foreign_key => 'input_port_uuid'
|
20
31
|
end
|
21
32
|
|
33
|
+
# Subclass of {Port} to represent output ports.
|
22
34
|
class OutputPort < Port
|
35
|
+
# @!attribute output_connections
|
36
|
+
# The connections receiving messages from this {OutputPort}.
|
37
|
+
# @return [Array<Connection>]
|
23
38
|
has_many :output_connections, :class_name => 'RFlow::Configuration::Connection', :primary_key => 'uuid', :foreign_key => 'output_port_uuid'
|
39
|
+
|
40
|
+
# @!attribute connections
|
41
|
+
# Synonym for {output_connections}.
|
42
|
+
# @return [Array<Connection>]
|
24
43
|
has_many :connections, :class_name => 'RFlow::Configuration::Connection', :primary_key => 'uuid', :foreign_key => 'output_port_uuid'
|
25
44
|
end
|
26
45
|
end
|
@@ -3,11 +3,13 @@ require 'rflow/configuration/uuid_keyed'
|
|
3
3
|
|
4
4
|
class RFlow
|
5
5
|
class Configuration
|
6
|
+
# Represents a setting in the SQLite database.
|
6
7
|
class Setting < ConfigurationItem
|
7
8
|
include ActiveModel::Validations
|
8
9
|
|
9
10
|
self.primary_key = 'name'
|
10
11
|
|
12
|
+
# Default settings.
|
11
13
|
DEFAULTS = {
|
12
14
|
'rflow.application_name' => 'rflow',
|
13
15
|
'rflow.application_directory_path' => '.',
|
@@ -18,6 +20,7 @@ class RFlow
|
|
18
20
|
'rflow.log_level' => 'INFO',
|
19
21
|
}
|
20
22
|
|
23
|
+
private
|
21
24
|
DIRECTORY_PATHS = [
|
22
25
|
'rflow.application_directory_path',
|
23
26
|
'rflow.pid_directory_path',
|
@@ -52,6 +55,9 @@ class RFlow
|
|
52
55
|
end
|
53
56
|
end
|
54
57
|
|
58
|
+
public
|
59
|
+
# Look up a {Setting} by name from the SQLite database.
|
60
|
+
# @return [Setting]
|
55
61
|
def self.[](name)
|
56
62
|
Setting.find(name).value rescue nil
|
57
63
|
end
|
@@ -3,20 +3,26 @@ require 'rflow/configuration/uuid_keyed'
|
|
3
3
|
|
4
4
|
class RFlow
|
5
5
|
class Configuration
|
6
|
+
# Represents a process shard in the SQLite database.
|
6
7
|
class Shard < ConfigurationItem
|
7
8
|
include UUIDKeyed
|
8
9
|
include ActiveModel::Validations
|
9
10
|
|
11
|
+
# Exception for when the shard is invalid.
|
10
12
|
class ShardInvalid < StandardError; end
|
11
13
|
|
14
|
+
# @!attribute components
|
15
|
+
# The {Component}s that are to run in this {Shard}.
|
16
|
+
# @return [Array<Component>]
|
12
17
|
has_many :components, :primary_key => 'uuid', :foreign_key => 'shard_uuid'
|
13
18
|
|
14
19
|
validates_uniqueness_of :name
|
15
20
|
validates_numericality_of :count, :only_integer => true, :greater_than => 0
|
16
21
|
end
|
17
22
|
|
18
|
-
#
|
23
|
+
# Subclass of {Shard} representing a shard instantiated by a process.
|
19
24
|
class ProcessShard < Shard; end
|
25
|
+
# Subclass of {Shard} representing a shard instantiated by a thread.
|
20
26
|
class ThreadShard < Shard; end
|
21
27
|
end
|
22
28
|
end
|
@@ -2,7 +2,12 @@ require 'uuidtools'
|
|
2
2
|
|
3
3
|
class RFlow
|
4
4
|
class Configuration
|
5
|
+
# Mixin for any {ConfigurationItem} that has a UUID key.
|
6
|
+
# Sets +primary_key+ column to be +uuid+ and initializes the
|
7
|
+
# UUID on creation.
|
8
|
+
# @!visibility private
|
5
9
|
module UUIDKeyed
|
10
|
+
# @!visibility private
|
6
11
|
def self.included(base)
|
7
12
|
base.class_eval do
|
8
13
|
self.primary_key = 'uuid'
|
data/lib/rflow/connection.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'rflow/message'
|
2
2
|
|
3
3
|
class RFlow
|
4
|
+
# Represents an RFlow connection from one component to another.
|
4
5
|
class Connection
|
5
6
|
class << self
|
7
|
+
# Build an appropriate subclass of {Connection} based on the configuration.
|
8
|
+
# @return [Connection]
|
6
9
|
def build(config)
|
7
10
|
case config.type
|
8
11
|
when 'RFlow::Configuration::ZMQConnection'
|
@@ -15,7 +18,22 @@ class RFlow
|
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
18
|
-
|
21
|
+
# The reference to the connection's configuration.
|
22
|
+
# @return [Configuration::Connection]
|
23
|
+
attr_accessor :config
|
24
|
+
|
25
|
+
# The connection's UUID.
|
26
|
+
# @return [String]
|
27
|
+
attr_accessor :uuid
|
28
|
+
|
29
|
+
# The connection's name.
|
30
|
+
# @return [String]
|
31
|
+
attr_accessor :name
|
32
|
+
|
33
|
+
# The connection's options Hash.
|
34
|
+
# @return [Hash]
|
35
|
+
attr_accessor :options
|
36
|
+
|
19
37
|
attr_writer :recv_callback
|
20
38
|
protected
|
21
39
|
attr_reader :recv_callback
|
@@ -28,44 +46,55 @@ class RFlow
|
|
28
46
|
@options = config.options
|
29
47
|
end
|
30
48
|
|
31
|
-
# Subclass and implement to be able to handle future
|
49
|
+
# Subclass and implement to be able to handle future +recv+
|
32
50
|
# methods. Will only be called in the context of a running
|
33
|
-
# EventMachine reactor
|
51
|
+
# EventMachine reactor.
|
52
|
+
# @return [void]
|
34
53
|
def connect_input!
|
35
54
|
raise NotImplementedError, 'Raw connections do not support connect_input. Please subclass and define a connect_input method.'
|
36
55
|
end
|
37
56
|
|
38
|
-
# Subclass and implement to be able to handle future
|
57
|
+
# Subclass and implement to be able to handle future +send+
|
39
58
|
# methods. Will only be called in the context of a running
|
40
|
-
# EventMachine reactor
|
59
|
+
# EventMachine reactor.
|
60
|
+
# @return [void]
|
41
61
|
def connect_output!
|
42
62
|
raise NotImplementedError, 'Raw connections do not support connect_output. Please subclass and define a connect_output method.'
|
43
63
|
end
|
44
64
|
|
45
65
|
# Subclass and implement to handle outgoing messages. The message
|
46
|
-
# will be a RFlow::Message object and the subclasses are expected
|
66
|
+
# will be a {RFlow::Message} object and the subclasses are expected
|
47
67
|
# to marshal it up into something that will be unmarshalled on the
|
48
|
-
# other side
|
68
|
+
# other side.
|
69
|
+
# @return [void]
|
49
70
|
def send_message(message)
|
50
71
|
raise NotImplementedError, 'Raw connections do not support send_message. Please subclass and define a send_message method.'
|
51
72
|
end
|
52
73
|
|
53
74
|
# Parent component will set this attribute if it expects to
|
54
|
-
#
|
55
|
-
# (recv_callback.call(message)) when it gets a new message, which
|
75
|
+
# receive messages. {Connection} subclass should call it
|
76
|
+
# (<tt>recv_callback.call(message)</tt>) when it gets a new message, which
|
56
77
|
# will be transmitted back to the parent component's
|
57
|
-
# process_message method.
|
58
|
-
# deserializing whatever was on the wire into a RFlow::Message object
|
78
|
+
# {Component#process_message} method. Subclass is responsible for
|
79
|
+
# deserializing whatever was on the wire into a {RFlow::Message} object.
|
80
|
+
# @return [Proc]
|
59
81
|
def recv_callback
|
60
82
|
@recv_callback ||= Proc.new {|message|}
|
61
83
|
end
|
62
84
|
|
85
|
+
# If we are connected to an {Component::InputPort} subport, the key for that subport.
|
86
|
+
# @return [String]
|
63
87
|
def input_port_key; config.input_port_key; end
|
88
|
+
|
89
|
+
# If we are connected to an {Component::OutputPort} subport, the key for that subport.
|
90
|
+
# @return [String]
|
64
91
|
def output_port_key; config.output_port_key; end
|
65
92
|
end
|
66
93
|
|
67
94
|
# Primarily for testing purposes. Captures whatever messages are sent on it.
|
68
95
|
class MessageCollectingConnection < Connection
|
96
|
+
# The messages that were collected.
|
97
|
+
# @return [Array<RFlow::Message>]
|
69
98
|
attr_accessor :messages
|
70
99
|
|
71
100
|
def initialize
|
@@ -73,6 +102,8 @@ class RFlow
|
|
73
102
|
@messages = []
|
74
103
|
end
|
75
104
|
|
105
|
+
# Override of {send_message} which adds the message to {messages}.
|
106
|
+
# @return [void]
|
76
107
|
def send_message(message)
|
77
108
|
@messages << message
|
78
109
|
end
|
@@ -88,6 +119,8 @@ class RFlow
|
|
88
119
|
@target_port = target_port
|
89
120
|
end
|
90
121
|
|
122
|
+
# Override of {send_message} which forwards the message to the target port.
|
123
|
+
# @return [void]
|
91
124
|
def send_message(message)
|
92
125
|
@target_port.send_message(message)
|
93
126
|
end
|
@@ -104,6 +137,8 @@ class RFlow
|
|
104
137
|
@target_port = target_port
|
105
138
|
end
|
106
139
|
|
140
|
+
# Override of {send_message} which forwards the message to the target port.
|
141
|
+
# @return [void]
|
107
142
|
def send_message(message)
|
108
143
|
@receiver.process_message(@target_port, nil, self, message)
|
109
144
|
end
|
@@ -9,11 +9,16 @@ require 'rflow/broker'
|
|
9
9
|
require 'sys/filesystem'
|
10
10
|
|
11
11
|
class RFlow
|
12
|
+
# Contains all connections classes.
|
12
13
|
module Connections
|
14
|
+
# Represents a ZeroMQ connection.
|
13
15
|
class ZMQConnection < RFlow::Connection
|
14
16
|
class << self
|
17
|
+
# The ZeroMQ context object.
|
18
|
+
# @return [EM::ZeroMQ::Context]
|
15
19
|
attr_accessor :zmq_context
|
16
20
|
|
21
|
+
# @!visibility private
|
17
22
|
def create_zmq_context
|
18
23
|
version = LibZMQ::version
|
19
24
|
RFlow.logger.debug { "Creating a new ZeroMQ context; ZeroMQ version is #{version[:major]}.#{version[:minor]}.#{version[:patch]}" }
|
@@ -23,12 +28,13 @@ class RFlow
|
|
23
28
|
EM::ZeroMQ::Context.new(1)
|
24
29
|
end
|
25
30
|
|
26
|
-
# Returns the current ZeroMQ context object or creates it if it does not exist.
|
27
31
|
def zmq_context
|
28
32
|
@zmq_context ||= create_zmq_context
|
29
33
|
end
|
30
34
|
end
|
31
35
|
|
36
|
+
# The ZeroMQ context object.
|
37
|
+
# @return [EM::ZeroMQ::Context]
|
32
38
|
def zmq_context; ZMQConnection.zmq_context; end
|
33
39
|
|
34
40
|
private
|
@@ -41,6 +47,8 @@ class RFlow
|
|
41
47
|
zmq_context # cause the ZMQ context to be created before the reactor is running
|
42
48
|
end
|
43
49
|
|
50
|
+
# Hook up the input to the real ZeroMQ sockets.
|
51
|
+
# @return [void]
|
44
52
|
def connect_input!
|
45
53
|
RFlow.logger.debug "Connecting input #{uuid} with #{options.find_all {|k, v| k.to_s =~ /input/}}"
|
46
54
|
check_address(options['input_address'])
|
@@ -65,6 +73,8 @@ class RFlow
|
|
65
73
|
input_socket
|
66
74
|
end
|
67
75
|
|
76
|
+
# Hook up the output to the real ZeroMQ sockets.
|
77
|
+
# @return [void]
|
68
78
|
def connect_output!
|
69
79
|
RFlow.logger.debug "Connecting output #{uuid} with #{options.find_all {|k, v| k.to_s =~ /output/}}"
|
70
80
|
check_address(options['output_address'])
|
@@ -74,6 +84,8 @@ class RFlow
|
|
74
84
|
output_socket
|
75
85
|
end
|
76
86
|
|
87
|
+
# Send a message along the connection into ZeroMQ.
|
88
|
+
# @return [void]
|
77
89
|
def send_message(message)
|
78
90
|
RFlow.logger.debug "#{name}: Sending message of type '#{message.data_type_name.to_s}'"
|
79
91
|
|
@@ -123,6 +135,9 @@ class RFlow
|
|
123
135
|
end
|
124
136
|
end
|
125
137
|
|
138
|
+
# Subclass of {ZMQConnection} representing a brokered ZeroMQ connection
|
139
|
+
# (one where messages are sent to a separate process performing the
|
140
|
+
# many-to-many brokering function).
|
126
141
|
class BrokeredZMQConnection < ZMQConnection
|
127
142
|
end
|
128
143
|
|
@@ -139,6 +154,8 @@ class RFlow
|
|
139
154
|
super("broker-#{connection.name}", 'Broker')
|
140
155
|
end
|
141
156
|
|
157
|
+
# Start the broker process. Returns when things are shutting down.
|
158
|
+
# @return [void]
|
142
159
|
def run_process
|
143
160
|
version = LibZMQ::version
|
144
161
|
RFlow.logger.debug { "Creating a new ZeroMQ context; ZeroMQ version is #{version[:major]}.#{version[:minor]}.#{version[:patch]}" }
|