cztop 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|