cztop 0.1.0

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