cztop 0.10.0 → 0.11.0

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