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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +31 -0
  5. data/.yardopts +1 -0
  6. data/AUTHORS +1 -0
  7. data/CHANGES.md +3 -0
  8. data/Gemfile +10 -0
  9. data/Guardfile +61 -0
  10. data/LICENSE +5 -0
  11. data/Procfile +3 -0
  12. data/README.md +408 -0
  13. data/Rakefile +6 -0
  14. data/bin/console +7 -0
  15. data/bin/setup +7 -0
  16. data/ci-scripts/install-deps +9 -0
  17. data/cztop.gemspec +36 -0
  18. data/examples/ruby_actor/actor.rb +100 -0
  19. data/examples/simple_req_rep/rep.rb +12 -0
  20. data/examples/simple_req_rep/req.rb +35 -0
  21. data/examples/taxi_system/.gitignore +2 -0
  22. data/examples/taxi_system/Makefile +2 -0
  23. data/examples/taxi_system/README.gsl +115 -0
  24. data/examples/taxi_system/README.md +276 -0
  25. data/examples/taxi_system/broker.rb +98 -0
  26. data/examples/taxi_system/client.rb +34 -0
  27. data/examples/taxi_system/generate_keys.rb +24 -0
  28. data/examples/taxi_system/start_broker.sh +2 -0
  29. data/examples/taxi_system/start_clients.sh +11 -0
  30. data/lib/cztop/actor.rb +308 -0
  31. data/lib/cztop/authenticator.rb +97 -0
  32. data/lib/cztop/beacon.rb +96 -0
  33. data/lib/cztop/certificate.rb +176 -0
  34. data/lib/cztop/config/comments.rb +66 -0
  35. data/lib/cztop/config/serialization.rb +82 -0
  36. data/lib/cztop/config/traversing.rb +157 -0
  37. data/lib/cztop/config.rb +119 -0
  38. data/lib/cztop/frame.rb +158 -0
  39. data/lib/cztop/has_ffi_delegate.rb +85 -0
  40. data/lib/cztop/message/frames.rb +74 -0
  41. data/lib/cztop/message.rb +191 -0
  42. data/lib/cztop/monitor.rb +102 -0
  43. data/lib/cztop/poller.rb +334 -0
  44. data/lib/cztop/polymorphic_zsock_methods.rb +24 -0
  45. data/lib/cztop/proxy.rb +149 -0
  46. data/lib/cztop/send_receive_methods.rb +35 -0
  47. data/lib/cztop/socket/types.rb +207 -0
  48. data/lib/cztop/socket.rb +106 -0
  49. data/lib/cztop/version.rb +3 -0
  50. data/lib/cztop/z85.rb +157 -0
  51. data/lib/cztop/zsock_options.rb +334 -0
  52. data/lib/cztop.rb +55 -0
  53. data/perf/README.md +79 -0
  54. data/perf/inproc_lat.rb +49 -0
  55. data/perf/inproc_thru.rb +42 -0
  56. data/perf/local_lat.rb +35 -0
  57. data/perf/remote_lat.rb +26 -0
  58. 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
@@ -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
@@ -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