cztop 0.10.0 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 117c17b6787d700550999d0302f299aebc833902
4
- data.tar.gz: 1b81e1fb89f114c51d9646e439d606083cd6b446
3
+ metadata.gz: c4906ff9fd7c2ebd3fb3b33eadcab389799649ff
4
+ data.tar.gz: 0332b4b9a1fed07870e459298ca43fb82223acd8
5
5
  SHA512:
6
- metadata.gz: 07ed4e2fa8914018dfa2806c1df8bd10b4ba610348e96c79500edc811311fc2b4d58841cf9b5cac7d7d30d9a91219acf58f86f595fee493e4e6e02332fb6f38e
7
- data.tar.gz: b867d2966b91bc2b371f1a61386cb0d0899047e415f9753119852a55047c9c103da1acd302af8a5ae7efaa38c18a3dc66cb6fdc75df9d05b1152c547118fe6d3
6
+ metadata.gz: 8727db3d61b640eb5a2e0611086d180f6515418d7fd4ecebd6ff6fd76e15f82fb10116e2420121c4dc8279bf11b426604228971d03d646f244d8886735475db7
7
+ data.tar.gz: 838b219829c9df9f8fc04081d3e262360bda1cf02f5c5d7e8881c2593e4aa7921a1ecaea54fd0e344a091d0cc5e1338267375ab6758ab1eb60b652fc4db2bfd5
data/.travis.yml CHANGED
@@ -12,6 +12,7 @@ rvm:
12
12
  env:
13
13
  # recognized by czmq-ffi-gen's ci-scripts
14
14
  - CZMQ_VERSION=HEAD ZMQ_VERSION=HEAD
15
+ - CZMQ_VERSION=stable ZMQ_VERSION=stable
15
16
  matrix:
16
17
  allow_failures:
17
18
  - rvm: rbx
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ 0.11.0 (11/06/2016)
2
+ -----
3
+ * upgrade to czmq-ffi-gen's 0.12.x line (which includes the CZMQ v4.0.0 release)
4
+ * add support for RADIO/DISH and SCATTER/GATHER sockets
5
+ * add CZTop::Frame#group and #group= methods
6
+ * add CZTop::Metadata to encode/decode ZMTP metadata
7
+
1
8
  0.10.0 (10/24/2016)
2
9
  -----
3
10
  * add Socket#readable? and #writable?
data/README.md CHANGED
@@ -6,6 +6,10 @@ FFI binding of [CZMQ](https://github.com/zeromq/czmq) and has a focus on being
6
6
  easy to use for Rubyists (POLS) and providing first class support for security
7
7
  mechanisms (like CURVE).
8
8
 
9
+ You might wanna check out
10
+ [cztop-patterns](https://github.com/paddor/cztop-patterns). It's still very
11
+ new, but will contain some reusable patterns described in the Zguide.
12
+
9
13
  [![Build Status on Travis CI](https://travis-ci.org/paddor/cztop.svg?branch=master)](https://travis-ci.org/paddor/cztop?branch=master)
10
14
  [![Code Climate](https://codeclimate.com/repos/56677a7849f50a141c001784/badges/48f3cca3c62df9e4b17b/gpa.svg)](https://codeclimate.com/repos/56677a7849f50a141c001784/feed)
11
15
  [![Inline docs](http://inch-ci.org/github/paddor/cztop.svg?branch=master&style=shields)](http://inch-ci.org/github/paddor/cztop)
@@ -44,9 +48,11 @@ Here's an overview of the core classes:
44
48
  * [Frame](http://www.rubydoc.info/gems/cztop/CZTop/Frame)
45
49
  * [Message](http://www.rubydoc.info/gems/cztop/CZTop/Message)
46
50
  * [Monitor](http://www.rubydoc.info/gems/cztop/CZTop/Monitor) < Actor
51
+ * [Metadata](http://www.rubydoc.info/gems/cztop/CZTop/Metadata)
47
52
  * [Proxy](http://www.rubydoc.info/gems/cztop/CZTop/Proxy) < Actor
48
- * [Poller](http://www.rubydoc.info/gems/cztop/CZTop/Poller)
53
+ * [Poller](http://www.rubydoc.info/gems/cztop/CZTop/Poller) (based on `zmq_poller_*()` functions)
49
54
  * [Aggregated](http://www.rubydoc.info/gems/cztop/CZTop/Poller/Aggregated)
55
+ * [ZPoller](http://www.rubydoc.info/gems/cztop/CZTop/Poller/ZPoller)
50
56
  * [Socket](http://www.rubydoc.info/gems/cztop/CZTop/Socket)
51
57
  * [REQ](http://www.rubydoc.info/gems/cztop/CZTop/Socket/REQ) < Socket
52
58
  * [REP](http://www.rubydoc.info/gems/cztop/CZTop/Socket/REP) < Socket
@@ -62,6 +68,10 @@ Here's an overview of the core classes:
62
68
  * [STREAM](http://www.rubydoc.info/gems/cztop/CZTop/Socket/STREAM) < Socket
63
69
  * [CLIENT](http://www.rubydoc.info/gems/cztop/CZTop/Socket/CLIENT) < Socket
64
70
  * [SERVER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/SERVER) < Socket
71
+ * [RADIO](http://www.rubydoc.info/gems/cztop/CZTop/Socket/RADIO) < Socket
72
+ * [DISH](http://www.rubydoc.info/gems/cztop/CZTop/Socket/DISH) < Socket
73
+ * [SCATTER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/SCATTER) < Socket
74
+ * [GATHER](http://www.rubydoc.info/gems/cztop/CZTop/Socket/GATHER) < Socket
65
75
  * [Z85](http://www.rubydoc.info/gems/cztop/CZTop/Z85)
66
76
  * [Padded](http://www.rubydoc.info/gems/cztop/CZTop/Z85/Padded) < Z85
67
77
  * [Pipe](http://www.rubydoc.info/gems/cztop/CZTop/Z85/Pipe)
@@ -88,11 +98,12 @@ More information in the [API documentation](http://www.rubydoc.info/github/paddo
88
98
  * no need to manually pass some constant
89
99
  * but you can: `CZTop::Socket.new_by_type(:REP)`
90
100
  * e.g. `#subscribe` only exists on CZTop::Socket::SUB
91
- * SERVER and CLIENT ready
92
- * see CZTop::Socket::SERVER and CZTop::Socket::CLIENT
93
- * there are `#routing_id` and `#routing_id=` on the following classes:
101
+ * DRAFT API ready
102
+ * CLIENT/SERVER/RADIO/DISH/SCATTER/GATHER and other DRAFT methods are supported if the libraries (ZMQ/CZMQ) have been compiled with DRAFT APIs enabled (`--enable-drafts`)
103
+ * there is `#routing_id` and `#routing_id=` on the following classes:
94
104
  * CZTop::Message
95
105
  * CZTop::Frame
106
+ * there is `#group` and `#group=` on CZTop::Frame
96
107
  * ZMTP 3.1 heartbeat ready
97
108
  * `socket.options.heartbeat_ivl = 2000`
98
109
  * `socket.options.heartbeat_timeout = 8000`
@@ -101,12 +112,11 @@ More information in the [API documentation](http://www.rubydoc.info/github/paddo
101
112
 
102
113
  You'll need:
103
114
 
104
- * CZMQ > 3.0.2 (currently built from master)
105
- * ZMQ >= 4.2 (currently built from master)
106
-
107
- For security mechanisms like CURVE, you'll need:
115
+ * CZMQ >= 4.0.0
116
+ * ZMQ >= 4.2.0
108
117
 
109
- * [libsodium](https://github.com/jedisct1/libsodium)
118
+ For security mechanisms like CURVE, it's recommended to use Libsodium. However,
119
+ ZMQ can be compiled with tweetnacl enabled.
110
120
 
111
121
  To install on OSX using homebrew, run:
112
122
 
@@ -4,6 +4,5 @@ mkdir -p vendor
4
4
  cd vendor
5
5
  git clone --depth 1 https://github.com/paddor/czmq-ffi-gen
6
6
  cd czmq-ffi-gen
7
- ci-scripts/install-libsodium
8
7
  ci-scripts/install-libzmq
9
8
  ci-scripts/install-libczmq
data/cztop.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_runtime_dependency "czmq-ffi-gen", "~> 0.10.0"
21
+ spec.add_runtime_dependency "czmq-ffi-gen", "~> 0.12.0"
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.10"
24
24
  spec.add_development_dependency "rake", "~> 10.0"
data/lib/cztop/frame.rb CHANGED
@@ -137,13 +137,13 @@ module CZTop
137
137
  ffi_delegate :size
138
138
 
139
139
  # Gets the routing ID.
140
- # @note This only set when the frame came from a {CZTop::Socket::SERVER}
141
- # socket.
140
+ # @note This only set when the frame has been read from
141
+ # a {CZTop::Socket::SERVER} socket.
142
142
  # @return [Integer] the routing ID, or 0 if unset
143
143
  ffi_delegate :routing_id
144
144
 
145
145
  # Sets a new routing ID.
146
- # @note This is used when the frame is sent to a {CZTop::Socket::SERVER}
146
+ # @note This is used when the frame is sent via a {CZTop::Socket::CLIENT}
147
147
  # socket.
148
148
  # @param new_routing_id [Integer] new routing ID
149
149
  # @raise [RangeError] if new routing ID is out of +uint32_t+ range
@@ -154,5 +154,27 @@ module CZTop
154
154
  raise RangeError if new_routing_id < 0
155
155
  ffi_delegate.set_routing_id(new_routing_id)
156
156
  end
157
+
158
+ # Gets the group (radio/dish pattern).
159
+ # @note This only set when the frame has been read from
160
+ # a {CZTop::Socket::DISH} socket.
161
+ # @return [String] the group
162
+ # @return [nil] when no group has been set
163
+ def group
164
+ group = ffi_delegate.group
165
+ return nil if group.empty?
166
+ group
167
+ end
168
+
169
+ # Sets a new group (radio/dish pattern).
170
+ # @note This is used when the frame is sent via a {CZTop::Socket::RADIO}
171
+ # socket.
172
+ # @param new_group [String] new group
173
+ # @raise [ArgumentError] if new group name is too long
174
+ # @return [new_group]
175
+ def group=(new_group)
176
+ rc = ffi_delegate.set_group(new_group)
177
+ raise_zmq_err("unable to set group to %p" % group) if rc == -1
178
+ end
157
179
  end
158
180
  end
@@ -15,8 +15,13 @@ module CZTop::HasFFIDelegate
15
15
  # Attaches an FFI delegate to the current (probably new) {CZTop} object.
16
16
  # @param ffi_delegate an instance of the corresponding class in the
17
17
  # CZMQ::FFI namespace
18
- # @raise [SystemCallError] if the FFI delegate's internal pointer is NULL
18
+ # @raise [SystemCallError, ArgumentError, ...] if the FFI delegate's
19
+ # internal pointer is NULL
19
20
  # @return [void]
21
+ # @note This only raises the correct exception when the creation of the new
22
+ # CZMQ object was the most recent thing done with the CZMQ library and
23
+ # thus CZMQ::FFI::Errors.errno is still reports the correct error number.
24
+ # @see raise_zmq_err
20
25
  def attach_ffi_delegate(ffi_delegate)
21
26
  raise_zmq_err(CZMQ::FFI::Errors.strerror) if ffi_delegate.null?
22
27
  @ffi_delegate = ffi_delegate
@@ -0,0 +1,89 @@
1
+ module CZTop
2
+ # Useful to encode and decode metadata as defined by ZMTP.
3
+ #
4
+ # ABNF:
5
+ #
6
+ # metadata = *property
7
+ # property = name value
8
+ # name = OCTET 1*255name-char
9
+ # name-char = ALPHA | DIGIT | "-" | "_" | "." | "+"
10
+ # value = 4OCTET *OCTET ; Size in network byte order
11
+ #
12
+ # @see https://rfc.zeromq.org/spec:23/ZMTP
13
+ class Metadata
14
+ VALUE_MAXLEN = 2**31-1
15
+
16
+ # Raised when decoding malformed metadata.
17
+ class InvalidData < StandardError
18
+ end
19
+
20
+ # regular expression used to validate property names
21
+ NAME_REGEX = /\A[[:alnum:]_.+-]{1,255}\Z/.freeze
22
+
23
+
24
+ # @param metadata [Hash<Symbol, #to_s>]
25
+ # @return [String]
26
+ def self.dump(metadata)
27
+ ic_names = Set.new
28
+ metadata.map do |k, v|
29
+ ic_name = k.to_sym.downcase
30
+ if ic_names.include?(ic_name)
31
+ raise ArgumentError, "property #{k.inspect}: duplicate name"
32
+ else
33
+ ic_names << ic_name
34
+ end
35
+ name = k.to_s
36
+ if NAME_REGEX !~ name
37
+ raise ArgumentError, "property #{k.inspect}: invalid name"
38
+ end
39
+ value = v.to_s
40
+ if value.bytesize > VALUE_MAXLEN
41
+ raise ArgumentError, "property #{k.inspect}: value too long"
42
+ end
43
+ [name.size, name, value.bytesize, value].pack("CA*NA*")
44
+ end.join
45
+ end
46
+
47
+ # @param string [String, Frame, #to_s]
48
+ # @return [Hash]
49
+ def self.load(string)
50
+ properties = {}
51
+ io = StringIO.new(string)
52
+ until io.eof?
53
+ # check for zero length names
54
+ name_length = io.read(1).unpack("C").first
55
+ raise InvalidData, "zero-length property name" if name_length.zero?
56
+ name = io.read(name_length)
57
+ name_sym = name.to_sym.downcase
58
+ if properties.has_key?(name_sym)
59
+ raise InvalidData, "property #{name.inspect}: duplicate name"
60
+ end
61
+
62
+ value_length = io.read(4).unpack("N").first
63
+ value = io.read(value_length)
64
+
65
+ properties[name_sym] = value
66
+ end
67
+ new(properties)
68
+ end
69
+
70
+ # @param properties [Hash<Symbol, String>] the properties as loaded by
71
+ # {load}
72
+ def initialize(properties)
73
+ @properties = properties
74
+ end
75
+
76
+ # Gets the value corresponding to a property name. The case of the name
77
+ # is insignificant.
78
+ # @param name [Symbol, String] the property name
79
+ # @return [String] the value
80
+ def [](name)
81
+ @properties[name.to_sym.downcase]
82
+ end
83
+
84
+ # @return [Hash<Symbol, String] all properties
85
+ def to_h
86
+ @properties
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,126 @@
1
+ module CZTop
2
+ # This is a poller which is able to provide a list of readable and a list
3
+ # of writable sockets. This is useful for when you need to process socket
4
+ # events in batch, rather than one per event loop iteration.
5
+ #
6
+ # In particular, this is needed in Celluloid::ZMQ, where in a call to
7
+ # Celluloid::ZMQ::Reactor#run_once all readable/writable sockets need to
8
+ # be processed.
9
+ #
10
+ # = Implementation
11
+ #
12
+ # It wraps a {CZTop::Poller} and just does the following to support
13
+ # getting an array of readable/writable sockets:
14
+ #
15
+ # * in {#wait}, poll with given timeout
16
+ # * in case there was an event:
17
+ # * deregister the corresponding event(s) on the registered socket
18
+ # * poll again with zero timeout until no more sockets
19
+ # * repeat and accumulate results into two lists
20
+ #
21
+ # = Forwarded Methods
22
+ #
23
+ # The following methods are defined on this class too, and calls are
24
+ # forwarded directly to the actual {CZTop::Poller} instance:
25
+ #
26
+ # * {CZTop::Poller#add}
27
+ # * {CZTop::Poller#add_reader}
28
+ # * {CZTop::Poller#add_writer}
29
+ # * {CZTop::Poller#modify}
30
+ # * {CZTop::Poller#remove}
31
+ # * {CZTop::Poller#remove_reader}
32
+ # * {CZTop::Poller#remove_writer}
33
+ # * {CZTop::Poller#sockets}
34
+ #
35
+ class Poller::Aggregated
36
+
37
+ # @return [CZTop::Poller.new] the associated (regular) poller
38
+ attr_reader :poller
39
+
40
+ # @return [Array<CZTop::Socket>] readable sockets
41
+ attr_reader :readables
42
+
43
+ # @return [Array<CZTop::Socket>] writable sockets
44
+ attr_reader :writables
45
+
46
+ extend Forwardable
47
+ def_delegators :@poller,
48
+ :add,
49
+ :add_reader,
50
+ :add_writer,
51
+ :modify,
52
+ :remove,
53
+ :remove_reader,
54
+ :remove_writer,
55
+ :sockets
56
+
57
+ # Initializes the aggregated poller.
58
+ # @param poller [CZTop::Poller] the wrapped poller
59
+ def initialize(poller = CZTop::Poller.new)
60
+ @readables = []
61
+ @writables = []
62
+ @poller = poller
63
+ end
64
+
65
+ # Forgets all previous event information (which sockets are
66
+ # readable/writable) and waits for events anew. After getting the first
67
+ # event, {CZTop::Poller#wait} is called again with a zero-timeout to get
68
+ # all pending events to extract them into the aggregated lists of
69
+ # readable and writable sockets.
70
+ #
71
+ # For every event, the corresponding event mask flag is disabled for the
72
+ # associated socket, so it won't turn up again. Finally, all event masks
73
+ # are restored to what they were before the call to this method.
74
+ #
75
+ # @param timeout [Integer] how long to wait in ms, or 0 to avoid blocking,
76
+ # or -1 to wait indefinitely
77
+ # @return [Boolean] whether there have been any events
78
+ def wait(timeout = -1)
79
+ @readables = []
80
+ @writables = []
81
+ @event_masks = {}
82
+
83
+ if event = @poller.wait(timeout)
84
+ extract(event)
85
+
86
+ # get all other pending events, if any, but no more blocking
87
+ while event = @poller.wait(0)
88
+ extract(event)
89
+ end
90
+
91
+ restore_event_masks
92
+ return true
93
+ end
94
+ return false
95
+ end
96
+
97
+ private
98
+
99
+ # Extracts the event information, adds the socket to the correct list(s)
100
+ # and modifies the socket's event mask for the socket to not turn up
101
+ # again during the next call(s) to {CZTop::Poller#wait} within {#wait}.
102
+ #
103
+ # @param event [CZTop::Poller::Event]
104
+ # @return [void]
105
+ def extract(event)
106
+ event_mask = poller.event_mask_for_socket(event.socket)
107
+ @event_masks[event.socket] = event_mask
108
+ if event.readable?
109
+ @readables << event.socket
110
+ event_mask &= 0xFFFF ^ CZTop::Poller::ZMQ::POLLIN
111
+ end
112
+ if event.writable?
113
+ @writables << event.socket
114
+ event_mask &= 0xFFFF ^ CZTop::Poller::ZMQ::POLLOUT
115
+ end
116
+ poller.modify(event.socket, event_mask)
117
+ end
118
+
119
+ # Restores the event mask for all registered sockets to the state they
120
+ # were before the call to {#wait}.
121
+ # @return [void]
122
+ def restore_event_masks
123
+ @event_masks.each { |socket, mask| poller.modify(socket, mask) }
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,78 @@
1
+ module CZTop
2
+
3
+ # CZTop's interface to the low-level +zmq_poller_*()+ functions.
4
+ module Poller::ZMQ
5
+
6
+ POLLIN = 1
7
+ POLLOUT = 2
8
+ POLLERR = 4
9
+
10
+ extend ::FFI::Library
11
+ lib_name = 'libzmq'
12
+ lib_paths = ['/usr/local/lib', '/opt/local/lib', '/usr/lib64']
13
+ .map { |path| "#{path}/#{lib_name}.#{::FFI::Platform::LIBSUFFIX}" }
14
+ ffi_lib lib_paths + [lib_name]
15
+
16
+ # This represents a +zmq_poller_event_t+ as in:
17
+ #
18
+ # typedef struct zmq_poller_event_t
19
+ # {
20
+ # void *socket;
21
+ # int fd;
22
+ # void *user_data;
23
+ # short events;
24
+ # } zmq_poller_event_t;
25
+ class PollerEvent < FFI::Struct
26
+ layout :socket, :pointer,
27
+ :fd, :int,
28
+ :user_data, :pointer,
29
+ :events, :short
30
+
31
+ # @return [Boolean] whether the socket is readable
32
+ def readable?
33
+ (self[:events] & POLLIN) > 0
34
+ end
35
+
36
+ # @return [Boolean] whether the socket is writable
37
+ def writable?
38
+ (self[:events] & POLLOUT) > 0
39
+ end
40
+ end
41
+
42
+ #ZMQ_EXPORT void *zmq_poller_new (void);
43
+ #ZMQ_EXPORT int zmq_poller_destroy (void **poller_p);
44
+ #ZMQ_EXPORT int zmq_poller_add (void *poller, void *socket, void *user_data, short events);
45
+ #ZMQ_EXPORT int zmq_poller_modify (void *poller, void *socket, short events);
46
+ #ZMQ_EXPORT int zmq_poller_remove (void *poller, void *socket);
47
+ #ZMQ_EXPORT int zmq_poller_wait (void *poller, zmq_poller_event_t *event, long timeout);
48
+
49
+ # Gracefully attaches a function. If it's not available, this creates
50
+ # a placeholder class method which, when called, simply raises
51
+ # NotImplementedError with a helpful message.
52
+ def self.attach_function(function_nickname, function_name, *args)
53
+ super
54
+ rescue ::FFI::NotFoundError
55
+ if $VERBOSE || $DEBUG
56
+ warn "CZTop: The ZMQ function #{function_name}() is not available. Don't use CZTop::Poller."
57
+ end
58
+ define_singleton_method(function_nickname) do |*|
59
+ raise NotImplementedError, "compile ZMQ with --enable-drafts"
60
+ end
61
+ end
62
+
63
+ opts = {
64
+ blocking: true # only necessary on MRI to deal with the GIL.
65
+ }
66
+ attach_function :poller_new, :zmq_poller_new, [], :pointer, **opts
67
+ attach_function :poller_destroy, :zmq_poller_destroy,
68
+ [:pointer], :int, **opts
69
+ attach_function :poller_add, :zmq_poller_add,
70
+ [:pointer, :pointer, :pointer, :short], :int, **opts
71
+ attach_function :poller_modify, :zmq_poller_modify,
72
+ [:pointer, :pointer, :short], :int, **opts
73
+ attach_function :poller_remove, :zmq_poller_remove,
74
+ [:pointer, :pointer], :int, **opts
75
+ attach_function :poller_wait, :zmq_poller_wait,
76
+ [:pointer, :pointer, :long], :int, **opts
77
+ end
78
+ end
@@ -0,0 +1,113 @@
1
+ module CZTop
2
+ # This is the trivial poller based on zpoller. It only supports polling
3
+ # for readability, but it also supports doing that on CLIENT/SERVER sockets,
4
+ # which is useful for {CZTop::Poller}.
5
+ #
6
+ # @see http://api.zeromq.org/czmq3-0:zpoller
7
+ class Poller::ZPoller
8
+ include HasFFIDelegate
9
+ extend CZTop::HasFFIDelegate::ClassMethods
10
+ include ::CZMQ::FFI
11
+
12
+ # Initializes the Poller. At least one reader has to be given.
13
+ # @param reader [Socket, Actor] socket to poll for input
14
+ # @param readers [Socket, Actor] any additional sockets to poll for input
15
+ def initialize(reader, *readers)
16
+ @sockets = {} # to keep references and return same instances
17
+ ptr = Zpoller.new(reader,
18
+ *readers.flat_map {|r| [ :pointer, r ] },
19
+ :pointer, nil)
20
+ attach_ffi_delegate(ptr)
21
+ remember_socket(reader)
22
+ readers.each { |r| remember_socket(r) }
23
+ end
24
+
25
+ # Adds another reader socket to the poller.
26
+ # @param reader [Socket, Actor] socket to poll for input
27
+ # @return [void]
28
+ # @raise [SystemCallError] if this fails
29
+ def add(reader)
30
+ rc = ffi_delegate.add(reader)
31
+ raise_zmq_err("unable to add socket %p" % reader) if rc == -1
32
+ remember_socket(reader)
33
+ end
34
+
35
+ # Removes a reader socket from the poller.
36
+ # @param reader [Socket, Actor] socket to remove
37
+ # @return [void]
38
+ # @raise [ArgumentError] if socket was invalid, e.g. it wasn't registered
39
+ # in this poller
40
+ # @raise [SystemCallError] if this fails for another reason
41
+ def remove(reader)
42
+ rc = ffi_delegate.remove(reader)
43
+ raise_zmq_err("unable to remove socket %p" % reader) if rc == -1
44
+ forget_socket(reader)
45
+ end
46
+
47
+ # Waits and returns the first socket that becomes readable.
48
+ # @param timeout [Integer] how long to wait in ms, or 0 to avoid
49
+ # blocking, or -1 to wait indefinitely
50
+ # @return [Socket, Actor] first socket of interest
51
+ # @return [nil] if the timeout expired or
52
+ # @raise [Interrupt] if the timeout expired or
53
+ def wait(timeout = -1)
54
+ ptr = ffi_delegate.wait(timeout)
55
+ if ptr.null?
56
+ raise Interrupt if ffi_delegate.terminated
57
+ return nil
58
+ end
59
+ return socket_by_ptr(ptr)
60
+ end
61
+
62
+ # Tells the zpoller to ignore interrupts. By default, {#wait} will return
63
+ # immediately if it detects an interrupt (when +zsys_interrupted+ is set
64
+ # to something other than zero). Calling this method will supress this
65
+ # behavior.
66
+ # @return [void]
67
+ def ignore_interrupts
68
+ ffi_delegate.ignore_interrupts
69
+ end
70
+
71
+ # By default the poller stops if the process receives a SIGINT or SIGTERM
72
+ # signal. This makes it impossible to shut-down message based architectures
73
+ # like zactors. This method lets you switch off break handling. The default
74
+ # nonstop setting is off (false).
75
+ #
76
+ # Setting this will cause {#wait} to never raise.
77
+ #
78
+ # @param flag [Boolean] whether the poller should run nonstop
79
+ def nonstop=(flag)
80
+ ffi_delegate.set_nonstop(flag)
81
+ end
82
+
83
+ private
84
+
85
+ # Remembers the socket so a call to {#wait} can return with the exact same
86
+ # instance of {Socket}, and it also makes sure the socket won't get
87
+ # GC'd.
88
+ # @param socket [Socket, Actor] the socket instance to remember
89
+ # @return [void]
90
+ def remember_socket(socket)
91
+ @sockets[socket.to_ptr.to_i] = socket
92
+ end
93
+
94
+ # Forgets the socket because it has been removed from the poller.
95
+ # @param socket [Socket, Actor] the socket instance to forget
96
+ # @return [void]
97
+ def forget_socket(socket)
98
+ @sockets.delete(socket.to_ptr.to_i)
99
+ end
100
+
101
+ # Gets the previously remembered socket associated to the given pointer.
102
+ # @param ptr [FFI::Pointer] the pointer to a socket
103
+ # @return [Socket, Actor] the socket associated to the given pointer
104
+ # @raise [SystemCallError] if no socket is registered under given pointer
105
+ def socket_by_ptr(ptr)
106
+ @sockets[ptr.to_i] or
107
+ # NOTE: This should never happen, since #wait will return nil if
108
+ # +zpoller_wait+ returned NULL. But it's better to fail early in case
109
+ # it ever returns a wrong pointer.
110
+ raise_zmq_err("no socket known for pointer #{ptr.inspect}")
111
+ end
112
+ end
113
+ end
data/lib/cztop/poller.rb CHANGED
@@ -213,303 +213,4 @@ module CZTop
213
213
  end
214
214
  end
215
215
  end
216
-
217
- # This is a poller which is able to provide a list of readable and a list
218
- # of writable sockets. This is useful for when you need to process socket
219
- # events in batch, rather than one per event loop iteration.
220
- #
221
- # In particular, this is needed in Celluloid::ZMQ, where in a call to
222
- # Celluloid::ZMQ::Reactor#run_once all readable/writable sockets need to
223
- # be processed.
224
- #
225
- # = Implementation
226
- #
227
- # It wraps a {CZTop::Poller} and just does the following to support
228
- # getting an array of readable/writable sockets:
229
- #
230
- # * in {#wait}, poll with given timeout
231
- # * in case there was an event:
232
- # * deregister the corresponding event(s) on the registered socket
233
- # * poll again with zero timeout until no more sockets
234
- # * repeat and accumulate results into two lists
235
- #
236
- # = Forwarded Methods
237
- #
238
- # The following methods are defined on this class too, and calls are
239
- # forwarded directly to the actual {CZTop::Poller} instance:
240
- #
241
- # * {CZTop::Poller#add}
242
- # * {CZTop::Poller#add_reader}
243
- # * {CZTop::Poller#add_writer}
244
- # * {CZTop::Poller#modify}
245
- # * {CZTop::Poller#remove}
246
- # * {CZTop::Poller#remove_reader}
247
- # * {CZTop::Poller#remove_writer}
248
- # * {CZTop::Poller#sockets}
249
- #
250
- class Poller::Aggregated
251
-
252
- # @return [CZTop::Poller.new] the associated (regular) poller
253
- attr_reader :poller
254
-
255
- # @return [Array<CZTop::Socket>] readable sockets
256
- attr_reader :readables
257
-
258
- # @return [Array<CZTop::Socket>] writable sockets
259
- attr_reader :writables
260
-
261
- extend Forwardable
262
- def_delegators :@poller,
263
- :add,
264
- :add_reader,
265
- :add_writer,
266
- :modify,
267
- :remove,
268
- :remove_reader,
269
- :remove_writer,
270
- :sockets
271
-
272
- # Initializes the aggregated poller.
273
- # @param poller [CZTop::Poller] the wrapped poller
274
- def initialize(poller = CZTop::Poller.new)
275
- @readables = []
276
- @writables = []
277
- @poller = poller
278
- end
279
-
280
- # Forgets all previous event information (which sockets are
281
- # readable/writable) and waits for events anew. After getting the first
282
- # event, {CZTop::Poller#wait} is called again with a zero-timeout to get
283
- # all pending events to extract them into the aggregated lists of
284
- # readable and writable sockets.
285
- #
286
- # For every event, the corresponding event mask flag is disabled for the
287
- # associated socket, so it won't turn up again. Finally, all event masks
288
- # are restored to what they were before the call to this method.
289
- #
290
- # @param timeout [Integer] how long to wait in ms, or 0 to avoid blocking,
291
- # or -1 to wait indefinitely
292
- # @return [Boolean] whether there have been any events
293
- def wait(timeout = -1)
294
- @readables = []
295
- @writables = []
296
- @event_masks = {}
297
-
298
- if event = @poller.wait(timeout)
299
- extract(event)
300
-
301
- # get all other pending events, if any, but no more blocking
302
- while event = @poller.wait(0)
303
- extract(event)
304
- end
305
-
306
- restore_event_masks
307
- return true
308
- end
309
- return false
310
- end
311
-
312
- private
313
-
314
- # Extracts the event information, adds the socket to the correct list(s)
315
- # and modifies the socket's event mask for the socket to not turn up
316
- # again during the next call(s) to {CZTop::Poller#wait} within {#wait}.
317
- #
318
- # @param event [CZTop::Poller::Event]
319
- # @return [void]
320
- def extract(event)
321
- event_mask = poller.event_mask_for_socket(event.socket)
322
- @event_masks[event.socket] = event_mask
323
- if event.readable?
324
- @readables << event.socket
325
- event_mask &= 0xFFFF ^ CZTop::Poller::ZMQ::POLLIN
326
- end
327
- if event.writable?
328
- @writables << event.socket
329
- event_mask &= 0xFFFF ^ CZTop::Poller::ZMQ::POLLOUT
330
- end
331
- poller.modify(event.socket, event_mask)
332
- end
333
-
334
- # Restores the event mask for all registered sockets to the state they
335
- # were before the call to {#wait}.
336
- # @return [void]
337
- def restore_event_masks
338
- @event_masks.each { |socket, mask| poller.modify(socket, mask) }
339
- end
340
- end
341
-
342
- # CZTop's interface to the low-level +zmq_poll()+ function.
343
- module Poller::ZMQ
344
-
345
- POLLIN = 1
346
- POLLOUT = 2
347
- POLLERR = 4
348
-
349
- extend ::FFI::Library
350
- lib_name = 'libzmq'
351
- lib_paths = ['/usr/local/lib', '/opt/local/lib', '/usr/lib64']
352
- .map { |path| "#{path}/#{lib_name}.#{::FFI::Platform::LIBSUFFIX}" }
353
- ffi_lib lib_paths + [lib_name]
354
-
355
- # This represents a +zmq_poller_event_t+ as in:
356
- #
357
- # typedef struct zmq_poller_event_t
358
- # {
359
- # void *socket;
360
- # int fd;
361
- # void *user_data;
362
- # short events;
363
- # } zmq_poller_event_t;
364
- class PollerEvent < FFI::Struct
365
- layout :socket, :pointer,
366
- :fd, :int,
367
- :user_data, :pointer,
368
- :events, :short
369
-
370
- # @return [Boolean] whether the socket is readable
371
- def readable?
372
- (self[:events] & POLLIN) > 0
373
- end
374
-
375
- # @return [Boolean] whether the socket is writable
376
- def writable?
377
- (self[:events] & POLLOUT) > 0
378
- end
379
- end
380
-
381
- #ZMQ_EXPORT void *zmq_poller_new (void);
382
- #ZMQ_EXPORT int zmq_poller_destroy (void **poller_p);
383
- #ZMQ_EXPORT int zmq_poller_add (void *poller, void *socket, void *user_data, short events);
384
- #ZMQ_EXPORT int zmq_poller_modify (void *poller, void *socket, short events);
385
- #ZMQ_EXPORT int zmq_poller_remove (void *poller, void *socket);
386
- #ZMQ_EXPORT int zmq_poller_wait (void *poller, zmq_poller_event_t *event, long timeout);
387
-
388
- opts = {
389
- blocking: true # only necessary on MRI to deal with the GIL.
390
- }
391
- attach_function :poller_new, :zmq_poller_new, [], :pointer, **opts
392
- attach_function :poller_destroy, :zmq_poller_destroy,
393
- [:pointer], :int, **opts
394
- attach_function :poller_add, :zmq_poller_add,
395
- [:pointer, :pointer, :pointer, :short], :int, **opts
396
- attach_function :poller_modify, :zmq_poller_modify,
397
- [:pointer, :pointer, :short], :int, **opts
398
- attach_function :poller_remove, :zmq_poller_remove,
399
- [:pointer, :pointer], :int, **opts
400
- attach_function :poller_wait, :zmq_poller_wait,
401
- [:pointer, :pointer, :long], :int, **opts
402
- end
403
-
404
- # This is the trivial poller based on zpoller. It only supports polling
405
- # for readability, but it also supports doing that on CLIENT/SERVER sockets,
406
- # which is useful for {CZTop::Poller}.
407
- #
408
- # @see http://api.zeromq.org/czmq3-0:zpoller
409
- class Poller::ZPoller
410
- include HasFFIDelegate
411
- extend CZTop::HasFFIDelegate::ClassMethods
412
- include ::CZMQ::FFI
413
-
414
- # Initializes the Poller. At least one reader has to be given.
415
- # @param reader [Socket, Actor] socket to poll for input
416
- # @param readers [Socket, Actor] any additional sockets to poll for input
417
- def initialize(reader, *readers)
418
- @sockets = {} # to keep references and return same instances
419
- ptr = Zpoller.new(reader,
420
- *readers.flat_map {|r| [ :pointer, r ] },
421
- :pointer, nil)
422
- attach_ffi_delegate(ptr)
423
- remember_socket(reader)
424
- readers.each { |r| remember_socket(r) }
425
- end
426
-
427
- # Adds another reader socket to the poller.
428
- # @param reader [Socket, Actor] socket to poll for input
429
- # @return [void]
430
- # @raise [SystemCallError] if this fails
431
- def add(reader)
432
- rc = ffi_delegate.add(reader)
433
- raise_zmq_err("unable to add socket %p" % reader) if rc == -1
434
- remember_socket(reader)
435
- end
436
-
437
- # Removes a reader socket from the poller.
438
- # @param reader [Socket, Actor] socket to remove
439
- # @return [void]
440
- # @raise [ArgumentError] if socket was invalid, e.g. it wasn't registered
441
- # in this poller
442
- # @raise [SystemCallError] if this fails for another reason
443
- def remove(reader)
444
- rc = ffi_delegate.remove(reader)
445
- raise_zmq_err("unable to remove socket %p" % reader) if rc == -1
446
- forget_socket(reader)
447
- end
448
-
449
- # Waits and returns the first socket that becomes readable.
450
- # @param timeout [Integer] how long to wait in ms, or 0 to avoid
451
- # blocking, or -1 to wait indefinitely
452
- # @return [Socket, Actor] first socket of interest
453
- # @return [nil] if the timeout expired or
454
- # @raise [Interrupt] if the timeout expired or
455
- def wait(timeout = -1)
456
- ptr = ffi_delegate.wait(timeout)
457
- if ptr.null?
458
- raise Interrupt if ffi_delegate.terminated
459
- return nil
460
- end
461
- return socket_by_ptr(ptr)
462
- end
463
-
464
- # Tells the zpoller to ignore interrupts. By default, {#wait} will return
465
- # immediately if it detects an interrupt (when +zsys_interrupted+ is set
466
- # to something other than zero). Calling this method will supress this
467
- # behavior.
468
- # @return [void]
469
- def ignore_interrupts
470
- ffi_delegate.ignore_interrupts
471
- end
472
-
473
- # By default the poller stops if the process receives a SIGINT or SIGTERM
474
- # signal. This makes it impossible to shut-down message based architectures
475
- # like zactors. This method lets you switch off break handling. The default
476
- # nonstop setting is off (false).
477
- #
478
- # Setting this will cause {#wait} to never raise.
479
- #
480
- # @param flag [Boolean] whether the poller should run nonstop
481
- def nonstop=(flag)
482
- ffi_delegate.set_nonstop(flag)
483
- end
484
-
485
- private
486
-
487
- # Remembers the socket so a call to {#wait} can return with the exact same
488
- # instance of {Socket}, and it also makes sure the socket won't get
489
- # GC'd.
490
- # @param socket [Socket, Actor] the socket instance to remember
491
- # @return [void]
492
- def remember_socket(socket)
493
- @sockets[socket.to_ptr.to_i] = socket
494
- end
495
-
496
- # Forgets the socket because it has been removed from the poller.
497
- # @param socket [Socket, Actor] the socket instance to forget
498
- # @return [void]
499
- def forget_socket(socket)
500
- @sockets.delete(socket.to_ptr.to_i)
501
- end
502
-
503
- # Gets the previously remembered socket associated to the given pointer.
504
- # @param ptr [FFI::Pointer] the pointer to a socket
505
- # @return [Socket, Actor] the socket associated to the given pointer
506
- # @raise [SystemCallError] if no socket is registered under given pointer
507
- def socket_by_ptr(ptr)
508
- @sockets[ptr.to_i] or
509
- # NOTE: This should never happen, since #wait will return nil if
510
- # +zpoller_wait+ returned NULL. But it's better to fail early in case
511
- # it ever returns a wrong pointer.
512
- raise_zmq_err("no socket known for pointer #{ptr.inspect}")
513
- end
514
- end
515
216
  end
@@ -206,5 +206,62 @@ module CZTop
206
206
  attach_ffi_delegate(Zsock.new_stream(endpoints))
207
207
  end
208
208
  end
209
+
210
+ # Group-based pub/sub (vs topic-based). This is the publisher socket.
211
+ # @see https://github.com/zeromq/libzmq/pull/1727
212
+ class RADIO < Socket
213
+ # @param endpoints [String] endpoints to connect to
214
+ def initialize(endpoints = nil)
215
+ attach_ffi_delegate(Zsock.new_radio(endpoints))
216
+ end
217
+ end
218
+
219
+ # Group-based pub/sub (vs topic-based). This is the subscriber socket.
220
+ # @see https://github.com/zeromq/libzmq/pull/1727
221
+ class DISH < Socket
222
+ # @param endpoints [String] endpoints to connect to
223
+ def initialize(endpoints = nil)
224
+ attach_ffi_delegate(Zsock.new_dish(endpoints))
225
+ end
226
+
227
+ # Joins the given group.
228
+ # @param group [String] group to join, up to 15 characters
229
+ # @return [void]
230
+ # @raise [ArgumentError] when group name is invalid or group has already
231
+ # been joined before
232
+ # @raise [SystemCallError] in case of failure
233
+ def join(group)
234
+ rc = ffi_delegate.join(group)
235
+ raise_zmq_err("unable to join group %p" % group) if rc == -1
236
+ end
237
+
238
+ # Leaves the given group.
239
+ # @param group [String] group to leave
240
+ # @return [void]
241
+ # @raise [ArgumentError] when group wasn't joined before
242
+ # @raise [SystemCallError] in case of another failure
243
+ def leave(group)
244
+ rc = ffi_delegate.leave(group)
245
+ raise_zmq_err("unable to leave group %p" % group) if rc == -1
246
+ end
247
+ end
248
+
249
+ # Scatter/gather pattern.
250
+ # @see https://github.com/zeromq/libzmq/pull/1909
251
+ class SCATTER < Socket
252
+ # @param endpoints [String] endpoints to connect to
253
+ def initialize(endpoints = nil)
254
+ attach_ffi_delegate(Zsock.new_scatter(endpoints))
255
+ end
256
+ end
257
+
258
+ # Scatter/gather pattern.
259
+ # @see https://github.com/zeromq/libzmq/pull/1909
260
+ class GATHER < Socket
261
+ # @param endpoints [String] endpoints to connect to
262
+ def initialize(endpoints = nil)
263
+ attach_ffi_delegate(Zsock.new_gather(endpoints))
264
+ end
265
+ end
209
266
  end
210
267
  end
data/lib/cztop/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module CZTop
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.0"
3
3
  end
@@ -330,6 +330,18 @@ module CZTop
330
330
  #int zsock_tcp_keepalive_intvl (void *self);
331
331
  #int zsock_rcvmore (void *self);
332
332
  #char * zsock_last_endpoint (void *self);
333
+ #attach_function :zsock_connect_timeout, [:pointer], :int, **opts
334
+ #attach_function :zsock_handshake_ivl, [:pointer], :int, **opts
335
+ #attach_function :zsock_invert_matching, [:pointer], :int, **opts
336
+ #attach_function :zsock_multicast_maxtpdu, [:pointer], :int, **opts
337
+ #attach_function :zsock_socks_proxy, [:pointer], :pointer, **opts
338
+ #attach_function :zsock_tcp_maxrt, [:pointer], :int, **opts
339
+ #attach_function :zsock_thread_safe, [:pointer], :int, **opts
340
+ #attach_function :zsock_use_fd, [:pointer], :int, **opts
341
+ #attach_function :zsock_vmci_buffer_max_size, [:pointer], :int, **opts
342
+ #attach_function :zsock_vmci_buffer_min_size, [:pointer], :int, **opts
343
+ #attach_function :zsock_vmci_buffer_size, [:pointer], :int, **opts
344
+ #attach_function :zsock_vmci_connect_timeout, [:pointer], :int, **opts
333
345
  #
334
346
  #// Set socket options
335
347
  #void zsock_set_router_handover (void *self, int router_handover);
@@ -358,6 +370,24 @@ module CZTop
358
370
  #void zsock_set_tcp_keepalive_idle (void *self, int tcp_keepalive_idle);
359
371
  #void zsock_set_tcp_keepalive_cnt (void *self, int tcp_keepalive_cnt);
360
372
  #void zsock_set_tcp_keepalive_intvl (void *self, int tcp_keepalive_intvl);
373
+ #attach_function :zsock_set_connect_rid, [:pointer, :string], :void, **opts
374
+ #attach_function :zsock_set_connect_rid_bin, [:pointer, :pointer], :void, **opts
375
+ #attach_function :zsock_set_connect_timeout, [:pointer, :int], :void, **opts
376
+ #attach_function :zsock_set_handshake_ivl, [:pointer, :int], :void, **opts
377
+ #attach_function :zsock_set_invert_matching, [:pointer, :int], :void, **opts
378
+ #attach_function :zsock_set_multicast_maxtpdu, [:pointer, :int], :void, **opts
379
+ #attach_function :zsock_set_socks_proxy, [:pointer, :string], :void, **opts
380
+ #attach_function :zsock_set_stream_notify, [:pointer, :int], :void, **opts
381
+ #attach_function :zsock_set_tcp_maxrt, [:pointer, :int], :void, **opts
382
+ #attach_function :zsock_set_use_fd, [:pointer, :int], :void, **opts
383
+ #attach_function :zsock_set_vmci_buffer_max_size, [:pointer, :int], :void, **opts
384
+ #attach_function :zsock_set_vmci_buffer_min_size, [:pointer, :int], :void, **opts
385
+ #attach_function :zsock_set_vmci_buffer_size, [:pointer, :int], :void, **opts
386
+ #attach_function :zsock_set_vmci_connect_timeout, [:pointer, :int], :void, **opts
387
+ #attach_function :zsock_set_xpub_manual, [:pointer, :int], :void, **opts
388
+ #attach_function :zsock_set_xpub_nodrop, [:pointer, :int], :void, **opts
389
+ #attach_function :zsock_set_xpub_verboser, [:pointer, :int], :void, **opts
390
+ #attach_function :zsock_set_xpub_welcome_msg, [:pointer, :string], :void, **opts
361
391
  end
362
392
  end
363
393
  end
data/lib/cztop.rb CHANGED
@@ -31,6 +31,10 @@ require_relative 'cztop/config/comments'
31
31
  require_relative 'cztop/config/traversing'
32
32
  require_relative 'cztop/config/serialization'
33
33
  require_relative 'cztop/message/frames'
34
+ require_relative 'cztop/metadata'
35
+ require_relative 'cztop/poller/aggregated'
36
+ require_relative 'cztop/poller/zmq'
37
+ require_relative 'cztop/poller/zpoller'
34
38
  require_relative 'cztop/socket/types'
35
39
  require_relative 'cztop/z85/padded'
36
40
  require_relative 'cztop/z85/pipe'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cztop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-24 00:00:00.000000000 Z
11
+ date: 2016-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: czmq-ffi-gen
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.10.0
19
+ version: 0.12.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.10.0
26
+ version: 0.12.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -261,8 +261,12 @@ files:
261
261
  - lib/cztop/has_ffi_delegate.rb
262
262
  - lib/cztop/message.rb
263
263
  - lib/cztop/message/frames.rb
264
+ - lib/cztop/metadata.rb
264
265
  - lib/cztop/monitor.rb
265
266
  - lib/cztop/poller.rb
267
+ - lib/cztop/poller/aggregated.rb
268
+ - lib/cztop/poller/zmq.rb
269
+ - lib/cztop/poller/zpoller.rb
266
270
  - lib/cztop/polymorphic_zsock_methods.rb
267
271
  - lib/cztop/proxy.rb
268
272
  - lib/cztop/send_receive_methods.rb