cztop 0.1.0
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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +31 -0
- data/.yardopts +1 -0
- data/AUTHORS +1 -0
- data/CHANGES.md +3 -0
- data/Gemfile +10 -0
- data/Guardfile +61 -0
- data/LICENSE +5 -0
- data/Procfile +3 -0
- data/README.md +408 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +7 -0
- data/ci-scripts/install-deps +9 -0
- data/cztop.gemspec +36 -0
- data/examples/ruby_actor/actor.rb +100 -0
- data/examples/simple_req_rep/rep.rb +12 -0
- data/examples/simple_req_rep/req.rb +35 -0
- data/examples/taxi_system/.gitignore +2 -0
- data/examples/taxi_system/Makefile +2 -0
- data/examples/taxi_system/README.gsl +115 -0
- data/examples/taxi_system/README.md +276 -0
- data/examples/taxi_system/broker.rb +98 -0
- data/examples/taxi_system/client.rb +34 -0
- data/examples/taxi_system/generate_keys.rb +24 -0
- data/examples/taxi_system/start_broker.sh +2 -0
- data/examples/taxi_system/start_clients.sh +11 -0
- data/lib/cztop/actor.rb +308 -0
- data/lib/cztop/authenticator.rb +97 -0
- data/lib/cztop/beacon.rb +96 -0
- data/lib/cztop/certificate.rb +176 -0
- data/lib/cztop/config/comments.rb +66 -0
- data/lib/cztop/config/serialization.rb +82 -0
- data/lib/cztop/config/traversing.rb +157 -0
- data/lib/cztop/config.rb +119 -0
- data/lib/cztop/frame.rb +158 -0
- data/lib/cztop/has_ffi_delegate.rb +85 -0
- data/lib/cztop/message/frames.rb +74 -0
- data/lib/cztop/message.rb +191 -0
- data/lib/cztop/monitor.rb +102 -0
- data/lib/cztop/poller.rb +334 -0
- data/lib/cztop/polymorphic_zsock_methods.rb +24 -0
- data/lib/cztop/proxy.rb +149 -0
- data/lib/cztop/send_receive_methods.rb +35 -0
- data/lib/cztop/socket/types.rb +207 -0
- data/lib/cztop/socket.rb +106 -0
- data/lib/cztop/version.rb +3 -0
- data/lib/cztop/z85.rb +157 -0
- data/lib/cztop/zsock_options.rb +334 -0
- data/lib/cztop.rb +55 -0
- data/perf/README.md +79 -0
- data/perf/inproc_lat.rb +49 -0
- data/perf/inproc_thru.rb +42 -0
- data/perf/local_lat.rb +35 -0
- data/perf/remote_lat.rb +26 -0
- metadata +297 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
module CZTop
|
2
|
+
class Config
|
3
|
+
|
4
|
+
# Access this config item's comments.
|
5
|
+
# @note Note that comments are discarded when loading a config (either from
|
6
|
+
# a string or file) and thus, only the comments you add during runtime
|
7
|
+
# are accessible.
|
8
|
+
# @return [CommentsAccessor]
|
9
|
+
def comments
|
10
|
+
return CommentsAccessor.new(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Used to access a {Config}'s comments.
|
14
|
+
class CommentsAccessor
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
# @param config [Config]
|
18
|
+
def initialize(config)
|
19
|
+
@config = config
|
20
|
+
end
|
21
|
+
|
22
|
+
# Adds a new comment.
|
23
|
+
# @param new_comment [String]
|
24
|
+
# @return [self]
|
25
|
+
def <<(new_comment)
|
26
|
+
@config.ffi_delegate.set_comment("%s", :string, new_comment)
|
27
|
+
return self
|
28
|
+
end
|
29
|
+
|
30
|
+
# Deletes all comments for this {Config} item.
|
31
|
+
# @return [void]
|
32
|
+
def delete_all
|
33
|
+
@config.ffi_delegate.set_comment(nil)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Yields all comments for this {Config} item.
|
37
|
+
# @yieldparam comment [String]
|
38
|
+
# @return [void]
|
39
|
+
def each
|
40
|
+
while comment = _zlist.next
|
41
|
+
break if comment.null?
|
42
|
+
yield comment.read_string
|
43
|
+
end
|
44
|
+
rescue CZMQ::FFI::Zlist::DestroyedError
|
45
|
+
# there are no comments
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the number of comments for this {Config} item.
|
50
|
+
# @return [Integer] number of comments
|
51
|
+
def size
|
52
|
+
_zlist.size
|
53
|
+
rescue CZMQ::FFI::Zlist::DestroyedError
|
54
|
+
0
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Returns the Zlist to the list of comments for this config item.
|
60
|
+
# @return [CZMQ::FFI::Zlist] the zlist of comments for this config item
|
61
|
+
def _zlist
|
62
|
+
@config.ffi_delegate.comments
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Methods used around serialization of {CZTop::Config} items.
|
2
|
+
module CZTop::Config::Serialization
|
3
|
+
# Serialize to a string in the ZPL format.
|
4
|
+
# @return [String]
|
5
|
+
def to_s
|
6
|
+
ffi_delegate.str_save.read_string
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns the path/filename of the file this {Config} tree was loaded from.
|
10
|
+
# @return [String]
|
11
|
+
def filename
|
12
|
+
ffi_delegate.filename
|
13
|
+
end
|
14
|
+
|
15
|
+
# Some class methods for {Config} related to serialization.
|
16
|
+
module ClassMethods
|
17
|
+
# Loads a {Config} tree from a string.
|
18
|
+
# @param string [String] the tree
|
19
|
+
# @return [Config]
|
20
|
+
def from_string(string)
|
21
|
+
from_ffi_delegate CZMQ::FFI::Zconfig.str_load(string)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Loads a {Config} tree from a file.
|
25
|
+
# @param path [String, Pathname, #to_s] the path to the ZPL config file
|
26
|
+
# @raise [SystemCallError] if this fails
|
27
|
+
# @return [Config]
|
28
|
+
def load(path)
|
29
|
+
ptr = CZMQ::FFI::Zconfig.load(path.to_s)
|
30
|
+
return from_ffi_delegate(ptr) unless ptr.null?
|
31
|
+
CZTop::HasFFIDelegate.raise_zmq_err(
|
32
|
+
"error while reading the file %p" % path.to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Loads a {Config} tree from a marshalled string.
|
36
|
+
# @note This method is automatically used by Marshal.load.
|
37
|
+
# @param string [String] marshalled {Config}
|
38
|
+
# @return [Config]
|
39
|
+
def _load(string)
|
40
|
+
from_string(string)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Saves the Config tree to a file.
|
45
|
+
# @param path [String, Pathname, #to_s] the path to the ZPL config file
|
46
|
+
# @return [void]
|
47
|
+
# @raise [SystemCallError] if this fails
|
48
|
+
def save(path)
|
49
|
+
rc = ffi_delegate.save(path.to_s)
|
50
|
+
return if rc == 0
|
51
|
+
raise_zmq_err("error while saving to the file %s" % path)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Reload config tree from same file that it was previously loaded from.
|
55
|
+
# @raise [TypeError] if this is an in-memory config
|
56
|
+
# @raise [SystemCallError] if this fails (no existing data will be
|
57
|
+
# changed)
|
58
|
+
# @return [void]
|
59
|
+
def reload
|
60
|
+
# NOTE: can't use Zconfig.reload, as we won't get the self pointer that
|
61
|
+
# gets reassigned by zconfig_reload(). We can just use Zconfig.load and
|
62
|
+
# swap out the FFI delegate.
|
63
|
+
filename = filename() or
|
64
|
+
raise TypeError, "can't reload in-memory config"
|
65
|
+
ptr = CZMQ::FFI::Zconfig.load(filename)
|
66
|
+
return attach_ffi_delegate(ptr) unless ptr.null?
|
67
|
+
raise_zmq_err("error while reloading from the file %p" % filename)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Serialize (marshal) this Config and all its children.
|
71
|
+
#
|
72
|
+
# @note This method is automatically used by Marshal.dump.
|
73
|
+
# @return [String] marshalled {Config}
|
74
|
+
def _dump(_level)
|
75
|
+
to_s
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class CZTop::Config
|
80
|
+
include Serialization
|
81
|
+
extend Serialization::ClassMethods
|
82
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# Methods used to traverse a {CZTop::Config} tree.
|
2
|
+
module CZTop::Config::Traversing
|
3
|
+
|
4
|
+
# Calls the given block once for each {Config} item in the tree, starting
|
5
|
+
# with self.
|
6
|
+
#
|
7
|
+
# @yieldparam config [Config] the config item
|
8
|
+
# @yieldparam level [Integer] level of the item (self has level 0,
|
9
|
+
# its direct children have level 1)
|
10
|
+
# @return [Object] the block's return value
|
11
|
+
# @raise [ArgumentError] if no block given
|
12
|
+
# @raise [Exception] the block's exception, in case it raises (it won't
|
13
|
+
# call the block any more after that)
|
14
|
+
def execute
|
15
|
+
raise ArgumentError, "no block given" unless block_given?
|
16
|
+
exception = nil
|
17
|
+
block_value = nil
|
18
|
+
ret = nil
|
19
|
+
callback = CZMQ::FFI::Zconfig.fct do |zconfig, _arg, level|
|
20
|
+
begin
|
21
|
+
# NOTE: work around JRuby and Rubinius bug, where it'd keep calling
|
22
|
+
# this FFI::Function, even when the block `break`ed
|
23
|
+
if ret != -1
|
24
|
+
config = from_ffi_delegate(zconfig)
|
25
|
+
block_value = yield config, level
|
26
|
+
ret = 0 # report success to keep zconfig_execute() going
|
27
|
+
end
|
28
|
+
rescue
|
29
|
+
# remember exception, so we can raise it later to the ruby code
|
30
|
+
# (it can't be raised now, as we have to report failure to
|
31
|
+
# zconfig_execute())
|
32
|
+
exception = $!
|
33
|
+
|
34
|
+
ret = -1 # report failure to stop zconfig_execute() immediately
|
35
|
+
ensure
|
36
|
+
ret ||= -1 # in case of 'break'
|
37
|
+
end
|
38
|
+
ret
|
39
|
+
end
|
40
|
+
ffi_delegate.execute(callback, _arg = nil)
|
41
|
+
raise exception if exception
|
42
|
+
return block_value
|
43
|
+
end
|
44
|
+
|
45
|
+
# Access to this config item's direct children.
|
46
|
+
# @return [ChildrenAccessor]
|
47
|
+
def children
|
48
|
+
ChildrenAccessor.new(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Access to this config item's siblings.
|
52
|
+
# @note Only the "younger" (later in the ZPL file) config items are
|
53
|
+
# considered.
|
54
|
+
# @return [SiblingsAccessor]
|
55
|
+
def siblings
|
56
|
+
SiblingsAccessor.new(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Used to give access to a {Config} item's children or siblings.
|
60
|
+
# @abstract
|
61
|
+
class FamilyAccessor
|
62
|
+
include Enumerable
|
63
|
+
|
64
|
+
# @param config [Config] the relative starting point (either parent or
|
65
|
+
# an older sibling)
|
66
|
+
def initialize(config)
|
67
|
+
@config = config
|
68
|
+
end
|
69
|
+
|
70
|
+
# This is supposed to return the first relevant config item.
|
71
|
+
# @abstract
|
72
|
+
# @return [Config, nil]
|
73
|
+
def first; end
|
74
|
+
|
75
|
+
# Yields all direct children/younger siblings. Starts with {#first}, if
|
76
|
+
# set.
|
77
|
+
# @yieldparam config [Config]
|
78
|
+
def each
|
79
|
+
current = first()
|
80
|
+
return if current.nil?
|
81
|
+
yield current
|
82
|
+
current_delegate = current.ffi_delegate
|
83
|
+
while current_delegate = current_delegate.next
|
84
|
+
break if current_delegate.null?
|
85
|
+
yield CZTop::Config.from_ffi_delegate(current_delegate)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Recursively compares these config items with the ones of the other.
|
90
|
+
# @param other [FamilyAccessor]
|
91
|
+
def ==(other)
|
92
|
+
these = to_a
|
93
|
+
those = other.to_a
|
94
|
+
these.size == those.size && these.zip(those) do |this, that|
|
95
|
+
this.tree_equal?(that) or return false
|
96
|
+
end
|
97
|
+
return true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Accesses the younger siblings of a given {Config} item.
|
102
|
+
class SiblingsAccessor < FamilyAccessor
|
103
|
+
# Returns the first sibling.
|
104
|
+
# @return [Config]
|
105
|
+
# @return [nil] if no younger siblings
|
106
|
+
def first
|
107
|
+
ptr = @config.ffi_delegate.next
|
108
|
+
return nil if ptr.null?
|
109
|
+
CZTop::Config.from_ffi_delegate(ptr)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Accesses the direct children of a given {Config} item.
|
114
|
+
class ChildrenAccessor < FamilyAccessor
|
115
|
+
def first
|
116
|
+
ptr = @config.ffi_delegate.child
|
117
|
+
return nil if ptr.null?
|
118
|
+
CZTop::Config.from_ffi_delegate(ptr)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Adds a new Config item and yields it, so it can be configured in
|
122
|
+
# a block.
|
123
|
+
# @param name [String] name for new config item
|
124
|
+
# @param value [String] value for new config item
|
125
|
+
# @yieldparam [Config] the new config item, if block was given
|
126
|
+
# @return [Config] the new config item
|
127
|
+
def new(name = nil, value = nil)
|
128
|
+
config = CZTop::Config.new(name, value, parent: @config)
|
129
|
+
yield config if block_given?
|
130
|
+
config
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Finds a config item along a path, relative to the current item.
|
135
|
+
# @param path [String] path (leading slash is optional and will be
|
136
|
+
# ignored)
|
137
|
+
# @return [Config] the found config item
|
138
|
+
# @return [nil] if there's no config item under this path
|
139
|
+
def locate(path)
|
140
|
+
ptr = ffi_delegate.locate(path)
|
141
|
+
return nil if ptr.null?
|
142
|
+
from_ffi_delegate(ptr)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Finds last item at given level (0 = root).
|
146
|
+
# @return [Config] the last config item at given level
|
147
|
+
# @return [nil] if there's no config item at given level
|
148
|
+
def last_at_depth(level)
|
149
|
+
ptr = ffi_delegate.at_depth(level)
|
150
|
+
return nil if ptr.null?
|
151
|
+
from_ffi_delegate(ptr)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class CZTop::Config
|
156
|
+
include Traversing
|
157
|
+
end
|
data/lib/cztop/config.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
module CZTop
|
2
|
+
|
3
|
+
# Represents a CZMQ::FFI::Zconfig item.
|
4
|
+
# @see http://rfc.zeromq.org/spec:4/ZPL
|
5
|
+
class Config
|
6
|
+
include HasFFIDelegate
|
7
|
+
extend CZTop::HasFFIDelegate::ClassMethods
|
8
|
+
|
9
|
+
# Initializes a new {Config} item. Takes an optional block to initialize
|
10
|
+
# the item further.
|
11
|
+
# @param name [String] config item name
|
12
|
+
# @param value [String] config item value
|
13
|
+
# @param parent [Config] parent config item
|
14
|
+
# @yieldparam config [self]
|
15
|
+
# @note If parent is given, the native child will be destroyed when the
|
16
|
+
# native parent is destroyed (and not when the child's corresponding
|
17
|
+
# {Config} object is garbage collected).
|
18
|
+
def initialize(name = nil, value = nil, parent: nil)
|
19
|
+
if parent
|
20
|
+
parent = parent.ffi_delegate if parent.is_a?(Config)
|
21
|
+
delegate = ::CZMQ::FFI::Zconfig.new(name, parent)
|
22
|
+
attach_ffi_delegate(delegate)
|
23
|
+
|
24
|
+
# NOTE: this delegate must not be freed automatically, because the
|
25
|
+
# parent will free it.
|
26
|
+
delegate.__undef_finalizer
|
27
|
+
else
|
28
|
+
delegate = ::CZMQ::FFI::Zconfig.new(name, nil)
|
29
|
+
attach_ffi_delegate(delegate)
|
30
|
+
end
|
31
|
+
|
32
|
+
self.value = value if value
|
33
|
+
yield self if block_given?
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!group ZPL attributes
|
37
|
+
|
38
|
+
# Gets the name.
|
39
|
+
# @return [String] name of the config item
|
40
|
+
def name
|
41
|
+
ptr = ffi_delegate.name
|
42
|
+
return nil if ptr.null? # NOTE: for unnamed elements
|
43
|
+
ptr.read_string
|
44
|
+
end
|
45
|
+
|
46
|
+
# Sets a new name.
|
47
|
+
# @param new_name [String, #to_s]
|
48
|
+
# @return [new_name]
|
49
|
+
def name=(new_name)
|
50
|
+
ffi_delegate.set_name(new_name.to_s)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Get the value of the config item.
|
54
|
+
# @return [String]
|
55
|
+
# @note This returns an empty string if the value is unset.
|
56
|
+
def value
|
57
|
+
ptr = ffi_delegate.value
|
58
|
+
return "" if ptr.null? # NOTE: for root elements
|
59
|
+
ptr.read_string
|
60
|
+
end
|
61
|
+
|
62
|
+
# Set or update the value of the config item.
|
63
|
+
# @param new_value [String, #to_s]
|
64
|
+
# @return [new_value]
|
65
|
+
def value=(new_value)
|
66
|
+
ffi_delegate.set_value("%s", :string, new_value.to_s)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Inspects this {Config} item.
|
70
|
+
# @return [String] shows class, name, and value
|
71
|
+
def inspect
|
72
|
+
"#<#{self.class.name}: name=#{name.inspect} value=#{value.inspect}>"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Update the value of a config item by path.
|
76
|
+
# @param path [String, #to_s] path to config item
|
77
|
+
# @param value [String, #to_s] path to config item
|
78
|
+
# @return [value]
|
79
|
+
def []=(path, value)
|
80
|
+
ffi_delegate.put(path.to_s, value.to_s)
|
81
|
+
end
|
82
|
+
alias_method :put, :[]=
|
83
|
+
|
84
|
+
# Get the value of the current config item.
|
85
|
+
# @param path [String, #to_s] path to config item
|
86
|
+
# @param default [String, #to_s] default value to return if config item
|
87
|
+
# doesn't exist
|
88
|
+
# @return [String]
|
89
|
+
# @return [default] if config item doesn't exist
|
90
|
+
# @note The default value is not returned when the config item exists but
|
91
|
+
# just doesn't have a value. In that case, it'll return the empty
|
92
|
+
# string.
|
93
|
+
def [](path, default = "")
|
94
|
+
ptr = ffi_delegate.get(path, default)
|
95
|
+
return nil if ptr.null?
|
96
|
+
ptr.read_string
|
97
|
+
end
|
98
|
+
alias_method :get, :[]
|
99
|
+
|
100
|
+
# @!endgroup
|
101
|
+
|
102
|
+
# Compares this config item to another. Only the name and value are
|
103
|
+
# considered. If you need to compare a config tree, use {#tree_equal?}.
|
104
|
+
# @param other [Config] the other config item
|
105
|
+
# @return [Boolean] whether they're equal
|
106
|
+
def ==(other)
|
107
|
+
name == other.name &&
|
108
|
+
value == other.value
|
109
|
+
end
|
110
|
+
|
111
|
+
# Compares this config tree to another tree or subtree. Names, values, and
|
112
|
+
# children are considered.
|
113
|
+
# @param other [Config] the other config tree
|
114
|
+
# @return [Boolean] whether they're equal
|
115
|
+
def tree_equal?(other)
|
116
|
+
self == other && self.children == other.children
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/cztop/frame.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
module CZTop
|
2
|
+
# Represents a CZMQ::FFI::Zframe, a part of a message.
|
3
|
+
#
|
4
|
+
# @note Dealing with frames (parts of a message) is pretty low-level. You'll
|
5
|
+
# probably not really need this functionality. It's only useful when you
|
6
|
+
# need to be able to receive and send single frames. Just use {Message}
|
7
|
+
# instead.
|
8
|
+
#
|
9
|
+
# @see http://api.zeromq.org/czmq3-0:zframe
|
10
|
+
class Frame
|
11
|
+
include HasFFIDelegate
|
12
|
+
extend CZTop::HasFFIDelegate::ClassMethods
|
13
|
+
|
14
|
+
# Initialize a new {Frame}.
|
15
|
+
# @param content [String] initial content
|
16
|
+
def initialize(content = nil)
|
17
|
+
attach_ffi_delegate(CZMQ::FFI::Zframe.new_empty)
|
18
|
+
self.content = content if content
|
19
|
+
end
|
20
|
+
|
21
|
+
FLAG_MORE = 1
|
22
|
+
FLAG_REUSE = 2
|
23
|
+
FLAG_DONTWAIT = 4
|
24
|
+
|
25
|
+
# Send {Message} to a {Socket}/{Actor}.
|
26
|
+
# @param destination [Socket, Actor] where to send this {Message} to
|
27
|
+
# @param more [Boolean] whether there are more {Frame}s to come for the
|
28
|
+
# same {Message}
|
29
|
+
# @param reuse [Boolean] whether this {Frame} will be used to send to
|
30
|
+
# other destinations later
|
31
|
+
# @param dontwait [Boolean] whether the operation should be performed in
|
32
|
+
# non-blocking mode
|
33
|
+
# @note If you don't specify +reuse: true+, do NOT use this {Frame}
|
34
|
+
# anymore afterwards. Its native counterpart will have been destroyed.
|
35
|
+
# @note This is low-level. Consider just sending a {Message}.
|
36
|
+
# @return [void]
|
37
|
+
# @raise [IO::EAGAINWaitWritable] if dontwait was set and the operation
|
38
|
+
# would have blocked right now
|
39
|
+
# @raise [SystemCallError] if there was some error. In that case, the
|
40
|
+
# native counterpart still exists and this {Frame} can be reused.
|
41
|
+
def send_to(destination, more: false, reuse: false, dontwait: false)
|
42
|
+
flags = 0
|
43
|
+
flags |= FLAG_MORE if more
|
44
|
+
flags |= FLAG_REUSE if reuse
|
45
|
+
flags |= FLAG_DONTWAIT if dontwait
|
46
|
+
|
47
|
+
# remember pointer, in case the zframe_t won't be destroyed
|
48
|
+
zframe_ptr = ffi_delegate.to_ptr
|
49
|
+
ret = CZMQ::FFI::Zframe.send(ffi_delegate, destination, flags)
|
50
|
+
|
51
|
+
if reuse || ret == -1
|
52
|
+
# zframe_t hasn't been destroyed yet: avoid memory leak.
|
53
|
+
attach_ffi_delegate(CZMQ::FFI::Zframe.__new(zframe_ptr, true))
|
54
|
+
# OPTIMIZE: reuse existing Zframe object by redefining its finalizer
|
55
|
+
end
|
56
|
+
|
57
|
+
if ret == -1
|
58
|
+
if dontwait && FFI.errno == Errno::EAGAIN::Errno
|
59
|
+
raise IO::EAGAINWaitWritable
|
60
|
+
end
|
61
|
+
|
62
|
+
raise_zmq_err
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Receive {Frame} from a {Socket}/{Actor}.
|
67
|
+
# @note This is low-level. Consider just receiving a {Message}.
|
68
|
+
# @return [Frame]
|
69
|
+
def self.receive_from(source)
|
70
|
+
from_ffi_delegate(CZMQ::FFI::Zframe.recv(source))
|
71
|
+
end
|
72
|
+
|
73
|
+
# @note This string is always binary. Use String#force_encoding if needed.
|
74
|
+
# @return [String] content as string (encoding = Encoding::BINARY)
|
75
|
+
def content
|
76
|
+
ffi_delegate.data.read_string(size)
|
77
|
+
end
|
78
|
+
alias_method :to_s, :content
|
79
|
+
|
80
|
+
# @return [Boolean] if this {Frame} has zero-sized content
|
81
|
+
def empty?
|
82
|
+
size.zero?
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sets new content of this {Frame}.
|
86
|
+
# @param new_content [String]
|
87
|
+
# @return [new_content]
|
88
|
+
def content=(new_content)
|
89
|
+
content_ptr = ::FFI::MemoryPointer.new(new_content.bytesize)
|
90
|
+
content_ptr.write_bytes(new_content)
|
91
|
+
ffi_delegate.reset(content_ptr, content_ptr.size)
|
92
|
+
# NOTE: FFI::MemoryPointer will autorelease
|
93
|
+
end
|
94
|
+
|
95
|
+
# Duplicates a frame.
|
96
|
+
# @return [Frame] new frame with same content
|
97
|
+
def dup
|
98
|
+
from_ffi_delegate(ffi_delegate.dup)
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Boolean] if the MORE indicator is set
|
102
|
+
# @note This happens when reading a frame from a {Socket} or using
|
103
|
+
# {#more=}.
|
104
|
+
def more?
|
105
|
+
ffi_delegate.more == 1
|
106
|
+
end
|
107
|
+
|
108
|
+
# Sets the MORE indicator.
|
109
|
+
# @param indicator [Boolean]
|
110
|
+
# @note This is NOT used when sending frame to socket.
|
111
|
+
# @see #send_to
|
112
|
+
# @return [indicator]
|
113
|
+
def more=(indicator)
|
114
|
+
ffi_delegate.set_more(indicator ? 1 : 0)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Compare to another frame.
|
118
|
+
# @param other [Frame]
|
119
|
+
# @return [Boolean] if this and the other frame have identical size and
|
120
|
+
# data
|
121
|
+
# @note If you need to compare to a string, as zframe_streq() would do,
|
122
|
+
# just get this frame's content first and compare that to the string.
|
123
|
+
# frame = CZTop::Frame.new("foobar")
|
124
|
+
# frame.to_s == "foobar" #=> true
|
125
|
+
# @example
|
126
|
+
# frame1 = Frame.new("foo")
|
127
|
+
# frame2 = Frame.new("foo")
|
128
|
+
# frame3 = Frame.new("bar")
|
129
|
+
# frame1 == frame2 #=> true
|
130
|
+
# frame1 == frame3 #=> false
|
131
|
+
# @note The {#more?} flag and the {#routing_id} are ignored.
|
132
|
+
def ==(other)
|
133
|
+
ffi_delegate.eq(other.ffi_delegate)
|
134
|
+
end
|
135
|
+
|
136
|
+
# @return [Integer] content length in bytes
|
137
|
+
ffi_delegate :size
|
138
|
+
|
139
|
+
# Gets the routing ID.
|
140
|
+
# @note This only set when the frame came from a {CZTop::Socket::SERVER}
|
141
|
+
# socket.
|
142
|
+
# @return [Integer] the routing ID, or 0 if unset
|
143
|
+
ffi_delegate :routing_id
|
144
|
+
|
145
|
+
# Sets a new routing ID.
|
146
|
+
# @note This is used when the frame is sent to a {CZTop::Socket::SERVER}
|
147
|
+
# socket.
|
148
|
+
# @param new_routing_id [Integer] new routing ID
|
149
|
+
# @raise [RangeError] if new routing ID is out of +uint32_t+ range
|
150
|
+
# @return [new_routing_id]
|
151
|
+
def routing_id=(new_routing_id)
|
152
|
+
# need to raise manually, as FFI lacks this feature.
|
153
|
+
# @see https://github.com/ffi/ffi/issues/473
|
154
|
+
raise RangeError if new_routing_id < 0
|
155
|
+
ffi_delegate.set_routing_id(new_routing_id)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'socket' # for SocketError
|
3
|
+
|
4
|
+
# This module is used to attach the low-level objects of classes within the
|
5
|
+
# CZMQ::FFI namespace (coming from the _czmq-ffi-gen_ gem) as delegates.
|
6
|
+
module CZTop::HasFFIDelegate
|
7
|
+
# @return [CZMQ::FFI::*] the attached delegate
|
8
|
+
attr_reader :ffi_delegate
|
9
|
+
|
10
|
+
# @return [FFI::Pointer] FFI delegate's pointer
|
11
|
+
def to_ptr
|
12
|
+
@ffi_delegate.to_ptr
|
13
|
+
end
|
14
|
+
|
15
|
+
# Attaches an FFI delegate to the current (probably new) {CZTop} object.
|
16
|
+
# @param ffi_delegate an instance of the corresponding class in the
|
17
|
+
# CZMQ::FFI namespace
|
18
|
+
# @raise [SystemCallError] if the FFI delegate's internal pointer is NULL
|
19
|
+
# @return [void]
|
20
|
+
def attach_ffi_delegate(ffi_delegate)
|
21
|
+
raise_zmq_err(CZMQ::FFI::Errors.strerror) if ffi_delegate.null?
|
22
|
+
@ffi_delegate = ffi_delegate
|
23
|
+
end
|
24
|
+
|
25
|
+
# Same as the counterpart in {ClassMethods}, but usable from within an
|
26
|
+
# instance.
|
27
|
+
# @see CZTop::FFIDelegate::ClassMethods#from_ffi_delegate
|
28
|
+
# @return [CZTop::*] the new object
|
29
|
+
def from_ffi_delegate(ffi_delegate)
|
30
|
+
self.class.from_ffi_delegate(ffi_delegate)
|
31
|
+
end
|
32
|
+
|
33
|
+
module_function
|
34
|
+
|
35
|
+
# Raises the appropriate exception for the reported ZMQ error.
|
36
|
+
#
|
37
|
+
# @param msg [String] error message
|
38
|
+
# @raise [ArgumentError] if EINVAL was reported
|
39
|
+
# @raise [Interrupt] if EINTR was reported
|
40
|
+
# @raise [SocketError] if EAGAIN was reported
|
41
|
+
# @raise [SystemCallError] any other reported error (appropriate
|
42
|
+
# SystemCallError subclass, if errno is known)
|
43
|
+
def raise_zmq_err(msg = CZMQ::FFI::Errors.strerror,
|
44
|
+
errno: CZMQ::FFI::Errors.errno)
|
45
|
+
|
46
|
+
# If the errno is known, the corresponding Errno::* exception is
|
47
|
+
# automatically constructed. Otherwise, it'll be a plain SystemCallError.
|
48
|
+
# In any case, #errno will return the corresponding errno.
|
49
|
+
raise SystemCallError.new(msg, errno), msg, caller
|
50
|
+
rescue Errno::EINVAL
|
51
|
+
raise ArgumentError, msg, caller
|
52
|
+
rescue Errno::EINTR
|
53
|
+
raise Interrupt, msg, caller
|
54
|
+
rescue Errno::EHOSTUNREACH
|
55
|
+
raise SocketError, msg, caller
|
56
|
+
end
|
57
|
+
|
58
|
+
# Some class methods related to FFI delegates.
|
59
|
+
module ClassMethods
|
60
|
+
include Forwardable
|
61
|
+
|
62
|
+
# Delegate specified instance method to the registered FFI delegate.
|
63
|
+
# @note It only takes one method name so it's easy to add some
|
64
|
+
# documentation for each delegated method.
|
65
|
+
# @param method [Symbol] method to delegate
|
66
|
+
# @return [void]
|
67
|
+
def ffi_delegate(method)
|
68
|
+
def_delegator(:@ffi_delegate, method)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Allocates a new instance and attaches the FFI delegate to it. This is
|
72
|
+
# useful if you already have an FFI delegate and need to attach it to a
|
73
|
+
# fresh high-level object.
|
74
|
+
# @return [CZTop::*] the fresh object
|
75
|
+
# @note #initialize won't be called on the fresh object. This works around
|
76
|
+
# the fact that #initialize usually assumes that no FFI delegate is
|
77
|
+
# attached yet and will try to do so (and also expect to be called in a
|
78
|
+
# specific way).
|
79
|
+
def from_ffi_delegate(ffi_delegate)
|
80
|
+
obj = allocate
|
81
|
+
obj.attach_ffi_delegate(ffi_delegate)
|
82
|
+
return obj
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|