cztop 0.2.1 → 0.3.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: beb64c08f14c0da1a3876bd7f95f0b25ae1d8ecd
4
- data.tar.gz: e6d4db01ac3823152f12a8a13ee5a2b5a08b190c
3
+ metadata.gz: 1619d296889181d095f1d88cf2fbf9b3f1d99f9c
4
+ data.tar.gz: c85c0ac08700a9f7558a9936acf8397688138248
5
5
  SHA512:
6
- metadata.gz: 72cb0f766f67e77aadb269d1c1f789e9cbd44e52d2beaecfab1794c5ead455517e624bc95fcd3f28f8d98893290bca45bc7a375f537f04bd2d0d7d86f45c0959
7
- data.tar.gz: e58951ec39fe151f5bc3ada5a9bfea55eb7ed2598f40386f9f7de6d82a80efb60567262ea57960dd7ea0087f0b32e7c06fccd0e4ff751d9fc7a03c5b502ed039
6
+ metadata.gz: 65d9cac510d5e66ae019827bd87c3959362f3e9a5ff5e4579864a2a56e5bfaef7b9de7e9b072e26c6862130b7e0b22f86c93a9f5cf0b498004c68fe1e140fc80
7
+ data.tar.gz: 371faea1cc8ccdb339033a2db20960cddfa9c5e40d59db4841013a06d3a72c0e70ccfe63b7e478724f46e4a963b956f045d52257f8e88787c38cbd588a836a1d
data/.travis.yml CHANGED
@@ -14,14 +14,6 @@ rvm:
14
14
  env:
15
15
  # recognized by czmq-ffi-gen's ci-scripts
16
16
  - CZMQ_VERSION=HEAD ZMQ_VERSION=HEAD
17
- - CZMQ_VERSION=3.0 ZMQ_VERSION=4.1
18
- matrix:
19
- exclude:
20
- # old ruby versions don't get to test bleeding edge functionality. basta.
21
- - rvm: 2.2.4
22
- env: CZMQ_VERSION=HEAD ZMQ_VERSION=HEAD
23
- - rvm: 2.1.8
24
- env: CZMQ_VERSION=HEAD ZMQ_VERSION=HEAD
25
17
  before_install:
26
18
  - PATH="/usr/lib/ccache:$PATH" # enable ccache
27
19
  - export LD_LIBRARY_PATH=$HOME/lib # custom libs (for execution)
data/CHANGES.md CHANGED
@@ -1,3 +1,11 @@
1
+ 0.3.0 (13/04/2016)
2
+ -----
3
+ * port CZTop::Poller to zmq_poller_*() functions so it supports thread-safe
4
+ sockets as well
5
+ * extract niche features to CZTop::Poller::Aggregated
6
+ * fix taxi system example
7
+ * drop support for CZMQ 3.0.2, ZMQ 4.0 and ZMQ 4.1
8
+
1
9
  0.2.1 (31/01/2016)
2
10
  -----
3
11
  * improve documentation
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ gemspec
3
3
 
4
4
  # useful when working on czmq-ffi-gen in parallel
5
5
  #gem "czmq-ffi-gen", git: "https://github.com/paddor/czmq-ffi-gen.git"
6
+ #gem "rspec-core", git: "file:///Users/paddor/src/ruby/rspec-core"
6
7
 
7
8
  group :development do
8
9
  gem 'rubocop', require: false
data/README.md CHANGED
@@ -27,6 +27,7 @@ the issues with them, from my point of view:
27
27
  * doesn't feel like Ruby
28
28
  * [mtortonesi/ruby-czmq](https://github.com/mtortonesi/ruby-czmq)
29
29
  * no tests
30
+ * no documentation
30
31
  * outdated
31
32
  * doesn't feel like Ruby
32
33
  * [chuckremes/ffi-rzmq](https://github.com/chuckremes/ffi-rzmq)
@@ -77,6 +78,7 @@ Here's an overview of the core classes:
77
78
  * [Monitor](http://www.rubydoc.info/gems/cztop/CZTop/Monitor) < Actor
78
79
  * [Proxy](http://www.rubydoc.info/gems/cztop/CZTop/Proxy) < Actor
79
80
  * [Poller](http://www.rubydoc.info/gems/cztop/CZTop/Poller)
81
+ * [Aggregated](http://www.rubydoc.info/gems/cztop/CZTop/Poller/Aggregated)
80
82
  * [Socket](http://www.rubydoc.info/gems/cztop/CZTop/Socket)
81
83
  * [REQ](http://www.rubydoc.info/gems/cztop/CZTop/Socket/REQ) < Socket
82
84
  * [REP](http://www.rubydoc.info/gems/cztop/CZTop/Socket/REP) < Socket
@@ -132,8 +134,8 @@ More information in the [API documentation](http://www.rubydoc.info/github/paddo
132
134
 
133
135
  You'll need:
134
136
 
135
- * CZMQ >= 3.0.2
136
- * ZMQ >= 4.0
137
+ * CZMQ > 3.0.2 (currently built from master)
138
+ * ZMQ >= 4.2 (currently built from master)
137
139
 
138
140
  For security mechanisms like CURVE, you'll need:
139
141
 
@@ -142,37 +144,12 @@ For security mechanisms like CURVE, you'll need:
142
144
  To install on OSX using homebrew, run:
143
145
 
144
146
  $ brew install libsodium
145
- $ brew install zmq --with-libsodium
146
- $ brew install czmq
147
+ $ brew install zmq --HEAD --with-libsodium
148
+ $ brew install czmq --HEAD
147
149
 
148
150
  If you're running Linux, go check [this page](http://zeromq.org/distro:_start)
149
151
  to get more help. Make sure to install CZMQ, not only ZMQ.
150
152
 
151
- **Warning**: To make use of the full feature set of CZTop (including
152
- CLIENT/SERVER sockets and ZMTP 3.1 heartbeats), you'll need to install both ZMQ
153
- and CZMQ from master, like this:
154
-
155
- # instead of the last two commands from above
156
- $ brew install zmq --with-libsodium --HEAD
157
- $ brew install czmq --HEAD
158
-
159
- See next section.
160
-
161
- ### Known Issues if using the current stable releases
162
-
163
- When using ZMQ 4.1/4.0:
164
- * no CLIENT/SERVER sockets. Don't try.
165
- * no ZMTP 3.1 heartbeats. Setting the options will have no effect.
166
-
167
- When using CZMQ 3.0:
168
- * don't use `Certificate#[]=` to unset meta data (by passing `nil`)
169
- * `zcert_unset_meta()` was added more recently for that case
170
- * see [zeromq/czmq#1248](https://github.com/zeromq/czmq/pull/1248)
171
- * if you use Beacon, make sure you also call `Beacon#configure`. Otherwise it closes STDIN when being destroyed.
172
- * see [zeromq/czmq#1281](https://github.com/zeromq/czmq/issues/1281)
173
- * no CLIENT/SERVER sockets. Don't try.
174
- * no ZMTP 3.1 heartbeats. Don't try.
175
-
176
153
  ### Supported Ruby versions
177
154
 
178
155
  See [.travis.yml](https://github.com/paddor/cztop/blob/master/.travis.yml) for a list of Ruby versions against which CZTop
@@ -355,8 +332,10 @@ $
355
332
  * [x] tested on CI
356
333
  * [x] ZMQ 4.1
357
334
  * [x] tested on CI
335
+ * as of April 2016, this isn't the case anymore
358
336
  * [x] ZMQ 4.0
359
337
  * [x] tested on CI
338
+ * as of March 2016, this isn't the case anymore
360
339
  * [ ] ZMQ 3.2
361
340
  * too big a pain ([d5172ab](https://github.com/paddor/czmq-ffi-gen/commit/d5172ab6db64999c50ba24f71569acf1dd45af51))
362
341
  * [x] support multiple versions of CZMQ
@@ -369,6 +348,7 @@ $
369
348
  * [x] adapt czmq-ffi-gen so it doesn't raise while `attach_function`, attach `zsys_has_curve()` instead (under same name)
370
349
  * [x] adapt test suite to skip affected test examples
371
350
  * [x] test on CI
351
+ * as of March, 2016, this isn't the case anymore
372
352
  * [x] port [Poller](http://www.rubydoc.info/gems/cztop/CZTop/Poller) to `zmq_poll()`
373
353
  * backwards compatible (`#add_reader`, `#add_writer`, `#wait` behave the same)
374
354
  * but in addition, it has `#readables` and `#writables` which return arrays of sockets
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.7.0"
21
+ spec.add_runtime_dependency "czmq-ffi-gen", "~> 0.8.3"
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.10"
24
24
  spec.add_development_dependency "rake", "~> 10.0"
@@ -36,11 +36,10 @@ end
36
36
  @driver_map = {} # driver name => routing ID
37
37
  Thread.new do
38
38
 
39
- # CZTop::Loop (zloop) doesn't work with SERVER sockets :(
40
39
  poller = CZTop::Poller.new(@socket)
41
40
  while true
42
41
  puts "waiting for socket to become readable ..."
43
- socket = poller.wait
42
+ socket = poller.simple_wait
44
43
  puts "socket is readable"
45
44
  msg = socket.receive
46
45
  puts "got message"
@@ -28,7 +28,7 @@ puts ">>> got #{welcome}."
28
28
 
29
29
  poller = CZTop::Poller.new(@socket)
30
30
  while true
31
- socket = poller.wait
31
+ socket = poller.simple_wait
32
32
  message = socket.receive
33
33
  puts ">>> received message: #{message[0].inspect}"
34
34
  end
data/lib/cztop/monitor.rb CHANGED
@@ -1,5 +1,3 @@
1
- require "pry"
2
-
3
1
  module CZTop
4
2
  # CZMQ monitor. Listen for socket events.
5
3
  #
data/lib/cztop/poller.rb CHANGED
@@ -1,99 +1,67 @@
1
1
  module CZTop
2
2
  # A non-trivial socket poller.
3
3
  #
4
- # It can poll for reading and writing, and supports getting back an array of
5
- # readable/writable sockets after the call to {#wait}. The reason for this
6
- # feature is to be able to use it in Celluloid::ZMQ, where in a call to
7
- # Celluloid::ZMQ::Reactor#run_once all readable/writable sockets need to be
8
- # processed.
4
+ # It can poll for reading and writing, and supports thread-safe sockets
5
+ # (SERVER/CLIENT/RADIO/DISH).
9
6
  #
10
7
  # This implementation is NOT based on CZMQ's +zpoller+. Reasons:
11
8
  #
12
9
  # * +zpoller+ can only poll for reading
13
10
  #
14
- # It's also NOT based on +zmq_poller()+. Reasons:
15
- #
16
- # * +zmq_poller()+ doesn't exist in older versions of ZMQ < 4.2
17
- #
18
- # Possible future implementation on +zmq_poller()+ might work like this, to
19
- # support getting an array of readable/writable sockets:
20
- #
21
- # * in {#wait}, poll with normal timeout
22
- # * then poll again with zero timeout until no more sockets, accumulate
23
- # results
24
- #
25
- # = Limitations
26
- #
27
- # This poller can't poll for writing on CLIENT/SERVER sockets.
28
- # Implementation could be adapted to support them using
29
- # {CZTop::Poller::ZPoller}, at least for reading. But it'd make the code
30
- # ugly.
31
- #
32
11
  class Poller
33
12
  include ::CZMQ::FFI
34
13
 
35
14
  # @param readers [Socket, Actor] sockets to poll for input
36
15
  def initialize(*readers)
37
- @readers = {}
38
- @writers = {}
39
- @readables = []
40
- @writables = []
41
- @rebuild_needed = true
42
- readers.each { |r| add_reader(r) }
43
- end
44
-
45
- # @return [Array<CZTop::Socket>] registered reader sockets
46
- def readers
47
- @readers.values
48
- end
49
-
50
- # @return [Array<CZTop::Socket>] registered writer sockets
51
- def writers
52
- @writers.values
16
+ @sockets = {} # needed to return the same socket objects
17
+ @events = {} # event masks for each socket
18
+ @poller_ptr = ZMQ.poller_new
19
+ ObjectSpace.define_finalizer(@poller_ptr,
20
+ Proc.new do
21
+ ptr_ptr = ::FFI::MemoryPointer.new :pointer
22
+ ptr_ptr.write_pointer(@poller_ptr)
23
+ ZMQ.poller_destroy(ptr_ptr)
24
+ end)
25
+ @event_ptr = FFI::MemoryPointer.new(ZMQ::PollerEvent)
26
+ readers.each { |r| add(r) }
53
27
  end
54
28
 
55
29
  # Adds a socket to be polled for reading.
56
30
  # @param socket [Socket, Actor] the socket
31
+ # @param events [Integer] events you're interested in (see constants in
32
+ # {ZMQ}
57
33
  # @return [void]
58
34
  # @raise [ArgumentError] if it's not a socket
59
- def add_reader(socket)
60
- raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
61
- ptr = Zsock.resolve(socket) # get low-level handle
62
- @readers[ptr.to_i] = socket
63
- @rebuild_needed = true
64
- end
65
-
66
- # Removes a previously registered reader socket. Won't raise if you're
67
- # trying to remove a socket that's not registered.
68
- # @param socket [Socket, Actor] the socket
69
- # @return [void]
70
- # @raise [ArgumentError] if it's not a socket
71
- def remove_reader(socket)
72
- raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
73
- ptr = Zsock.resolve(socket) # get low-level handle
74
- @readers.delete(ptr.to_i) and @rebuild_needed = true
35
+ def add(socket, events = ZMQ::POLLIN)
36
+ ptr = ptr_for_socket(socket)
37
+ rc = ZMQ.poller_add(@poller_ptr, ptr, nil, events)
38
+ HasFFIDelegate.raise_zmq_err if rc == -1
39
+ remember_socket(socket, events)
75
40
  end
76
41
 
77
- # Adds a socket to be polled for writing.
42
+ # Modifies the events of interest for the given socket.
78
43
  # @param socket [Socket, Actor] the socket
44
+ # @param events [Integer] events you're interested in (see constants in
45
+ # {ZMQ}
79
46
  # @return [void]
80
47
  # @raise [ArgumentError] if it's not a socket
81
- def add_writer(socket)
82
- raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
83
- ptr = Zsock.resolve(socket) # get low-level handle
84
- @writers[ptr.to_i] = socket
85
- @rebuild_needed = true
48
+ def modify(socket, events)
49
+ ptr = ptr_for_socket(socket)
50
+ rc = ZMQ.poller_modify(@poller_ptr, ptr, events)
51
+ HasFFIDelegate.raise_zmq_err if rc == -1
52
+ remember_socket(socket, events)
86
53
  end
87
54
 
88
- # Removes a previously registered writer socket. Won't raise if you're
55
+ # Removes a previously registered socket. Won't raise if you're
89
56
  # trying to remove a socket that's not registered.
90
57
  # @param socket [Socket, Actor] the socket
91
58
  # @return [void]
92
59
  # @raise [ArgumentError] if it's not a socket
93
- def remove_writer(socket)
94
- raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
95
- ptr = Zsock.resolve(socket) # get low-level handle
96
- @writers.delete(ptr.to_i) and @rebuild_needed = true
60
+ def remove(socket)
61
+ ptr = ptr_for_socket(socket)
62
+ rc = ZMQ.poller_remove(@poller_ptr, ptr)
63
+ HasFFIDelegate.raise_zmq_err if rc == -1
64
+ forget_socket(socket)
97
65
  end
98
66
 
99
67
  # Waits for registered sockets to become readable or writable, depending
@@ -101,236 +69,378 @@ module CZTop
101
69
  #
102
70
  # @param timeout [Integer] how long to wait in ms, or 0 to avoid blocking,
103
71
  # or -1 to wait indefinitely
104
- # @return [Socket, Actor] the first readable socket
72
+ # @return [Event] the first event of interest
105
73
  # @return [nil] if the timeout expired or
106
- # @raise [Interrupt] if the timeout expired or
74
+ # @raise [SystemCallError] if this failed
107
75
  def wait(timeout = -1)
108
- rebuild if @rebuild_needed
109
- @readables = @writables = nil
110
-
111
- num = ZMQ.poll(@items_ptr, @nitems, timeout)
112
- HasFFIDelegate.raise_zmq_err if num == -1
76
+ rc = ZMQ.poller_wait(@poller_ptr, @event_ptr, timeout)
77
+ if rc == -1
78
+ if CZMQ::FFI::Errors.errno != Errno::ETIMEDOUT::Errno
79
+ HasFFIDelegate.raise_zmq_err
80
+ end
81
+ return nil
82
+ end
83
+ return Event.new(self, @event_ptr)
84
+ end
113
85
 
114
- return nil if num == 0
115
- return readables[0] if readables.any?
86
+ # Simpler version of {#wait}, which just returns the first socket of
87
+ # interest, if any. This is useful if you either have only reader sockets,
88
+ # or only have writer sockets.
89
+ # @param timeout [Integer] how long to wait in ms, or 0 to avoid blocking,
90
+ # or -1 to wait indefinitely
91
+ # @return [Socket, Actor] first socket of interest
92
+ # @return [nil] if timeout expired
93
+ # @raise [SystemCallError] if this failed
94
+ def simple_wait(timeout = -1)
95
+ event = wait(timeout)
96
+ return event.socket if event
97
+ end
116
98
 
117
- # TODO: handle CLIENT/SERVER sockets using ZPoller
118
- # if threadsafe_sockets.any?
119
- # zpoller.wait(0)
120
- # end
99
+ # @param ptr [FFI::Pointer] pointer to the socket
100
+ # @return [Socket, Actor] socket corresponding to given pointer
101
+ def socket_for_ptr(ptr)
102
+ @sockets[ptr.to_i] or
103
+ # NOTE: This should never happen, since #wait will return nil if
104
+ # +zpoller_wait+ returned NULL. But it's better to fail early in case
105
+ # it ever returns a wrong pointer.
106
+ HasFFIDelegate.raise_zmq_err(
107
+ "no socket known for pointer #{ptr.inspect}")
121
108
  end
122
109
 
123
- # @return [Array<CZTop::Socket>] readable sockets (memoized)
124
- def readables
125
- @readables ||= @reader_items.select(&:readable?).map do |item|
126
- ptr = item[:socket]
127
- @readers[ ptr.to_i ]
128
- end
110
+ # @return [Array<CZTop::Socket>] all sockets registered with this poller
111
+ # @note The actual events registered for each sockets don't matter.
112
+ def sockets
113
+ @sockets.values
129
114
  end
130
115
 
131
- # @return [Array<CZTop::Socket>] writable sockets (memoized)
132
- def writables
133
- @writables ||= @writer_items.select(&:writable?).map do |item|
134
- ptr = item[:socket]
135
- @writers[ ptr.to_i ]
136
- end
116
+ def event_mask_for_socket(socket)
117
+ @events[socket] or
118
+ raise "no event mask known for socket %p" % socket
137
119
  end
138
120
 
139
121
  private
140
122
 
141
- # Rebuilds the list of +poll_item_t+.
142
- # @return [void]
143
- def rebuild
144
- @nitems = @readers.size + @writers.size
145
- @items_ptr = FFI::MemoryPointer.new(ZMQ::PollItem, @nitems)
146
- @items_ptr.autorelease = true
147
-
148
- # memory addresses
149
- mem = Enumerator.new do |y|
150
- @nitems.times { |i| y << @items_ptr + i * ZMQ::PollItem.size }
151
- end
123
+ # @param socket [Socket, Actor] the socket
124
+ # @return [FFI::Pointer] low-level handle
125
+ # @raise [ArgumentError] if argument is not a socket
126
+ def ptr_for_socket(socket)
127
+ raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
128
+ Zsock.resolve(socket)
129
+ end
152
130
 
153
- @reader_items = @readers.map{|_,s| new_item(mem.next, s, ZMQ::POLLIN) }
154
- @writer_items = @writers.map{|_,s| new_item(mem.next, s, ZMQ::POLLOUT) }
131
+ def remember_socket(socket, events)
132
+ @sockets[ptr_for_socket(socket).to_i] = socket
133
+ @events[socket] = events
134
+ end
155
135
 
156
- @rebuild_needed = false
136
+ def forget_socket(socket)
137
+ @sockets.delete(ptr_for_socket(socket).to_i)
138
+ @events.delete(socket)
157
139
  end
158
140
 
159
- # @param address [FFI::Pointer] allocated memory address for this item
160
- # @param socket [CZTop::Socket] socket we're interested in
161
- # @param events [Integer] the events we're interested in
162
- # @return [ZMQ::PollItem] a new item for
163
- def new_item(address, socket, events)
164
- item = ZMQ::PollItem.new(address)
165
- item[:socket] = Zsock.resolve(socket)
166
- item[:fd] = 0
167
- item[:events] = events
168
- item[:revents] = 0
169
- item
141
+ # Represents an event returned by {CZTop::Poller#wait}. This is useful to
142
+ # find out whether the associated socket is now readable or writable, in
143
+ # case you're interested in both. For a simpler variant, check out
144
+ # {CZTop::Poller#simple_wait}.
145
+ class Event
146
+ # @param poller [CZTop::Poller] the poller instance
147
+ # @param event_ptr [FFI::Pointer] pointer to the memory allocated for
148
+ # the event's data (a +zmq_poller_event_t+)
149
+ def initialize(poller, event_ptr)
150
+ @poller = poller
151
+ @poller_event = ZMQ::PollerEvent.new(event_ptr)
152
+ end
153
+
154
+ # @return [Socket, Actor] the associated socket
155
+ def socket
156
+ @socket ||= @poller.socket_for_ptr(@poller_event[:socket])
157
+ end
158
+
159
+ # @return [Boolean] whether it's readable
160
+ def readable?
161
+ @poller_event.readable?
162
+ end
163
+
164
+ # @return [Boolean] whether it's writable
165
+ def writable?
166
+ @poller_event.writable?
167
+ end
170
168
  end
169
+ end
171
170
 
172
- # CZTop's interface to the low-level +zmq_poll()+ function.
173
- module ZMQ
171
+ # This is a poller which is able to provide a list of readable and a list
172
+ # of writable sockets. This is useful for when you need to process socket
173
+ # events in batch, rather than one per event loop iteration.
174
+ #
175
+ # In particular, this is needed in Celluloid::ZMQ, where in a call to
176
+ # Celluloid::ZMQ::Reactor#run_once all readable/writable sockets need to
177
+ # be processed.
178
+ #
179
+ # = Implementation
180
+ #
181
+ # It wraps a {CZTop::Poller} and just does the following to support
182
+ # getting an array of readable/writable sockets:
183
+ #
184
+ # * in {#wait}, poll with given timeout
185
+ # * in case there was an event, poll again with zero timeout until no more
186
+ # sockets
187
+ # * accumulate results into two lists
188
+ #
189
+ class Poller::Aggregated
174
190
 
175
- POLL = 1
176
- POLLIN = 1
177
- POLLOUT = 2
178
- POLLERR = 4
191
+ # @return [CZTop::Poller.new] the associated (regular) poller
192
+ attr_reader :poller
179
193
 
180
- extend ::FFI::Library
181
- lib_name = 'libzmq'
182
- lib_paths = ['/usr/local/lib', '/opt/local/lib', '/usr/lib64']
183
- .map { |path| "#{path}/#{lib_name}.#{::FFI::Platform::LIBSUFFIX}" }
184
- ffi_lib lib_paths + [lib_name]
194
+ # @return [Array<CZTop::Socket>] readable sockets
195
+ attr_reader :readables
185
196
 
186
- # Represents a struct of type +zmq_pollitem_t+.
187
- class PollItem < FFI::Struct
188
- ##
189
- # shamelessly taken from https://github.com/mtortonesi/ruby-czmq-ffi
190
- #
197
+ # @return [Array<CZTop::Socket>] writable sockets
198
+ attr_reader :writables
191
199
 
200
+ # Initializes the aggregated poller.
201
+ # @param poller [CZTop::Poller] the wrapped poller
202
+ def initialize(poller = CZTop::Poller.new)
203
+ @readables = []
204
+ @writables = []
205
+ @poller = poller
206
+ end
192
207
 
193
- FD_TYPE = if FFI::Platform::IS_WINDOWS && FFI::Platform::ADDRESS_SIZE == 64
194
- # On Windows, zmq.h defines fd as a SOCKET, which is 64 bits on x64.
195
- :uint64
196
- else
197
- :int
198
- end
208
+ # Forgets all previous event information (which sockets are
209
+ # readable/writable) and waits for events anew. After getting the first
210
+ # event, {CZTop::Poller#wait} is called again with a zero-timeout to get
211
+ # all pending events to extract them into the aggregated lists of
212
+ # readable and writable sockets.
213
+ #
214
+ # For every event, the corresponding event mask flag is disabled for the
215
+ # associated socket, so it won't turn up again. Finally, all event masks
216
+ # are restored to what they were before the call to this method.
217
+ #
218
+ # @param timeout [Integer] how long to wait in ms, or 0 to avoid blocking,
219
+ # or -1 to wait indefinitely
220
+ # @return [Boolean] whether there have been any events
221
+ def wait(timeout = -1)
222
+ @readables = []
223
+ @writables = []
224
+ @event_masks = {}
199
225
 
200
- layout :socket, :pointer,
201
- :fd, FD_TYPE,
202
- :events, :short,
203
- :revents, :short
226
+ if event = @poller.wait(timeout)
227
+ extract(event)
204
228
 
205
- # @return [Boolean] whether the socket is readable
206
- def readable?
207
- (self[:revents] & POLLIN) > 0
229
+ # get all other pending events, if any, but no more blocking
230
+ while event = @poller.wait(0)
231
+ extract(event)
208
232
  end
209
233
 
210
- # @return [Boolean] whether the socket is writable
211
- def writable?
212
- (self[:revents] & POLLOUT) > 0
213
- end
234
+ restore_event_masks
235
+ return true
214
236
  end
215
-
216
- opts = {
217
- blocking: true # only necessary on MRI to deal with the GIL.
218
- }
219
-
220
- #ZMQ_EXPORT int zmq_poll (zmq_pollitem_t *items, int nitems, long timeout);
221
- attach_function :poll, :zmq_poll, [:pointer, :int, :long], :int, **opts
237
+ return false
222
238
  end
223
239
 
224
- # This is the trivial poller based on zpoller. It only supports polling
225
- # for reading, but it also supports doing that on CLIENT/SERVER sockets,
226
- # which is useful for {CZTop::Poller}.
240
+ private
241
+
242
+ # Extracts the event information, adds the socket to the correct list(s)
243
+ # and modifies the socket's event mask for the socket to not turn up
244
+ # again during the next call(s) to {CZTop::Poller#wait} within {#wait}.
227
245
  #
228
- # @see http://api.zeromq.org/czmq3-0:zpoller
229
- class ZPoller
230
- include HasFFIDelegate
231
- extend CZTop::HasFFIDelegate::ClassMethods
232
- include ::CZMQ::FFI
233
-
234
- # Initializes the Poller. At least one reader has to be given.
235
- # @param reader [Socket, Actor] socket to poll for input
236
- # @param readers [Socket, Actor] any additional sockets to poll for input
237
- def initialize(reader, *readers)
238
- @sockets = {} # to keep references and return same instances
239
- ptr = Zpoller.new(reader,
240
- *readers.flat_map {|r| [ :pointer, r ] },
241
- :pointer, nil)
242
- attach_ffi_delegate(ptr)
243
- remember_socket(reader)
244
- readers.each { |r| remember_socket(r) }
246
+ # @param event [CZTop::Poller::Event]
247
+ # @return [void]
248
+ def extract(event)
249
+ event_mask = poller.event_mask_for_socket(event.socket)
250
+ @event_masks[event.socket] = event_mask
251
+ if event.readable?
252
+ @readables << event.socket
253
+ event_mask &= 0xFFFF ^ CZTop::Poller::ZMQ::POLLIN
245
254
  end
246
-
247
- # Adds another reader socket to the poller.
248
- # @param reader [Socket, Actor] socket to poll for input
249
- # @return [void]
250
- # @raise [SystemCallError] if this fails
251
- def add(reader)
252
- rc = ffi_delegate.add(reader)
253
- raise_zmq_err("unable to add socket %p" % reader) if rc == -1
254
- remember_socket(reader)
255
+ if event.writable?
256
+ @writables << event.socket
257
+ event_mask &= 0xFFFF ^ CZTop::Poller::ZMQ::POLLOUT
255
258
  end
259
+ poller.modify(event.socket, event_mask)
260
+ end
256
261
 
257
- # Removes a reader socket from the poller.
258
- # @param reader [Socket, Actor] socket to remove
259
- # @return [void]
260
- # @raise [ArgumentError] if socket was invalid, e.g. it wasn't registered
261
- # in this poller
262
- # @raise [SystemCallError] if this fails for another reason
263
- def remove(reader)
264
- rc = ffi_delegate.remove(reader)
265
- raise_zmq_err("unable to remove socket %p" % reader) if rc == -1
266
- forget_socket(reader)
262
+ # Restores the event mask for all registered sockets to the state they
263
+ # were before the call to {#wait}.
264
+ # @return [void]
265
+ def restore_event_masks
266
+ @event_masks.each do |socket, mask|
267
+ poller.modify(socket, mask)
267
268
  end
269
+ end
270
+ end
268
271
 
269
- # Wait and return the first socket that becomes readable.
270
- # @param timeout [Integer] how long to wait in ms, or 0 to avoid blocking,
271
- # or -1 to wait indefinitely
272
- # @return [Socket, Actor]
273
- # @return [nil] if the timeout expired or
274
- # @raise [Interrupt] if the timeout expired or
275
- def wait(timeout = -1)
276
- ptr = ffi_delegate.wait(timeout)
277
- if ptr.null?
278
- raise Interrupt if ffi_delegate.terminated
279
- return nil
280
- end
281
- return socket_by_ptr(ptr)
282
- end
272
+ # CZTop's interface to the low-level +zmq_poll()+ function.
273
+ module Poller::ZMQ
274
+
275
+ POLL = 1
276
+ POLLIN = 1
277
+ POLLOUT = 2
278
+ POLLERR = 4
279
+
280
+ extend ::FFI::Library
281
+ lib_name = 'libzmq'
282
+ lib_paths = ['/usr/local/lib', '/opt/local/lib', '/usr/lib64']
283
+ .map { |path| "#{path}/#{lib_name}.#{::FFI::Platform::LIBSUFFIX}" }
284
+ ffi_lib lib_paths + [lib_name]
283
285
 
284
- # Tells the zpoller to ignore interrupts. By default, {#wait} will return
285
- # immediately if it detects an interrupt (when +zsys_interrupted+ is set
286
- # to something other than zero). Calling this method will supress this
287
- # behavior.
288
- # @return [void]
289
- def ignore_interrupts
290
- ffi_delegate.ignore_interrupts
286
+ # This represents a +zmq_poller_event_t+ as in:
287
+ #
288
+ # typedef struct zmq_poller_event_t
289
+ # {
290
+ # void *socket;
291
+ # int fd;
292
+ # void *user_data;
293
+ # short events;
294
+ # } zmq_poller_event_t;
295
+ class PollerEvent < FFI::Struct
296
+ layout :socket, :pointer,
297
+ :fd, :int,
298
+ :user_data, :pointer,
299
+ :events, :short
300
+
301
+ # @return [Boolean] whether the socket is readable
302
+ def readable?
303
+ (self[:events] & POLLIN) > 0
291
304
  end
292
305
 
293
- # By default the poller stops if the process receives a SIGINT or SIGTERM
294
- # signal. This makes it impossible to shut-down message based architectures
295
- # like zactors. This method lets you switch off break handling. The default
296
- # nonstop setting is off (false).
297
- #
298
- # Setting this will cause {#wait} to never raise.
299
- #
300
- # @param flag [Boolean] whether the poller should run nonstop
301
- def nonstop=(flag)
302
- ffi_delegate.set_nonstop(flag)
306
+ # @return [Boolean] whether the socket is writable
307
+ def writable?
308
+ (self[:events] & POLLOUT) > 0
303
309
  end
310
+ end
304
311
 
305
- private
312
+ #ZMQ_EXPORT void *zmq_poller_new (void);
313
+ #ZMQ_EXPORT int zmq_poller_destroy (void **poller_p);
314
+ #ZMQ_EXPORT int zmq_poller_add (void *poller, void *socket, void *user_data, short events);
315
+ #ZMQ_EXPORT int zmq_poller_modify (void *poller, void *socket, short events);
316
+ #ZMQ_EXPORT int zmq_poller_remove (void *poller, void *socket);
317
+ #ZMQ_EXPORT int zmq_poller_wait (void *poller, zmq_poller_event_t *event, long timeout);
318
+
319
+ opts = {
320
+ blocking: true # only necessary on MRI to deal with the GIL.
321
+ }
322
+ attach_function :poller_new, :zmq_poller_new, [], :pointer, **opts
323
+ attach_function :poller_destroy, :zmq_poller_destroy,
324
+ [:pointer], :int, **opts
325
+ attach_function :poller_add, :zmq_poller_add,
326
+ [:pointer, :pointer, :pointer, :short], :int, **opts
327
+ attach_function :poller_modify, :zmq_poller_modify,
328
+ [:pointer, :pointer, :short], :int, **opts
329
+ attach_function :poller_remove, :zmq_poller_remove,
330
+ [:pointer, :pointer], :int, **opts
331
+ attach_function :poller_wait, :zmq_poller_wait,
332
+ [:pointer, :pointer, :long], :int, **opts
333
+ end
306
334
 
307
- # Remembers the socket so a call to {#wait} can return with the exact same
308
- # instance of {Socket}, and it also makes sure the socket won't get
309
- # GC'd.
310
- # @param socket [Socket, Actor] the socket instance to remember
311
- # @return [void]
312
- def remember_socket(socket)
313
- @sockets[socket.to_ptr.to_i] = socket
314
- end
335
+ # This is the trivial poller based on zpoller. It only supports polling
336
+ # for reading, but it also supports doing that on CLIENT/SERVER sockets,
337
+ # which is useful for {CZTop::Poller}.
338
+ #
339
+ # @see http://api.zeromq.org/czmq3-0:zpoller
340
+ class Poller::ZPoller
341
+ include HasFFIDelegate
342
+ extend CZTop::HasFFIDelegate::ClassMethods
343
+ include ::CZMQ::FFI
315
344
 
316
- # Forgets the socket because it has been removed from the poller.
317
- # @param socket [Socket, Actor] the socket instance to forget
318
- # @return [void]
319
- def forget_socket(socket)
320
- @sockets.delete(socket.to_ptr.to_i)
321
- end
345
+ # Initializes the Poller. At least one reader has to be given.
346
+ # @param reader [Socket, Actor] socket to poll for input
347
+ # @param readers [Socket, Actor] any additional sockets to poll for input
348
+ def initialize(reader, *readers)
349
+ @sockets = {} # to keep references and return same instances
350
+ ptr = Zpoller.new(reader,
351
+ *readers.flat_map {|r| [ :pointer, r ] },
352
+ :pointer, nil)
353
+ attach_ffi_delegate(ptr)
354
+ remember_socket(reader)
355
+ readers.each { |r| remember_socket(r) }
356
+ end
357
+
358
+ # Adds another reader socket to the poller.
359
+ # @param reader [Socket, Actor] socket to poll for input
360
+ # @return [void]
361
+ # @raise [SystemCallError] if this fails
362
+ def add(reader)
363
+ rc = ffi_delegate.add(reader)
364
+ raise_zmq_err("unable to add socket %p" % reader) if rc == -1
365
+ remember_socket(reader)
366
+ end
367
+
368
+ # Removes a reader socket from the poller.
369
+ # @param reader [Socket, Actor] socket to remove
370
+ # @return [void]
371
+ # @raise [ArgumentError] if socket was invalid, e.g. it wasn't registered
372
+ # in this poller
373
+ # @raise [SystemCallError] if this fails for another reason
374
+ def remove(reader)
375
+ rc = ffi_delegate.remove(reader)
376
+ raise_zmq_err("unable to remove socket %p" % reader) if rc == -1
377
+ forget_socket(reader)
378
+ end
322
379
 
323
- # Gets the previously remembered socket associated to the given pointer.
324
- # @param ptr [FFI::Pointer] the pointer to a socket
325
- # @return [Socket, Actor] the socket associated to the given pointer
326
- # @raise [SystemCallError] if no socket is registered under given pointer
327
- def socket_by_ptr(ptr)
328
- @sockets[ptr.to_i] or
329
- # NOTE: This should never happen, since #wait will return nil if
330
- # +zpoller_wait+ returned NULL. But it's better to fail early in case
331
- # it ever returns a wrong pointer.
332
- raise_zmq_err("no socket known for pointer #{ptr.inspect}")
380
+ # Waits and returns the first socket that becomes readable.
381
+ # @param timeout [Integer] how long to wait in ms, or 0 to avoid
382
+ # blocking, or -1 to wait indefinitely
383
+ # @return [Socket, Actor] first socket of interest
384
+ # @return [nil] if the timeout expired or
385
+ # @raise [Interrupt] if the timeout expired or
386
+ def wait(timeout = -1)
387
+ ptr = ffi_delegate.wait(timeout)
388
+ if ptr.null?
389
+ raise Interrupt if ffi_delegate.terminated
390
+ return nil
333
391
  end
392
+ return socket_by_ptr(ptr)
393
+ end
394
+
395
+ # Tells the zpoller to ignore interrupts. By default, {#wait} will return
396
+ # immediately if it detects an interrupt (when +zsys_interrupted+ is set
397
+ # to something other than zero). Calling this method will supress this
398
+ # behavior.
399
+ # @return [void]
400
+ def ignore_interrupts
401
+ ffi_delegate.ignore_interrupts
402
+ end
403
+
404
+ # By default the poller stops if the process receives a SIGINT or SIGTERM
405
+ # signal. This makes it impossible to shut-down message based architectures
406
+ # like zactors. This method lets you switch off break handling. The default
407
+ # nonstop setting is off (false).
408
+ #
409
+ # Setting this will cause {#wait} to never raise.
410
+ #
411
+ # @param flag [Boolean] whether the poller should run nonstop
412
+ def nonstop=(flag)
413
+ ffi_delegate.set_nonstop(flag)
414
+ end
415
+
416
+ private
417
+
418
+ # Remembers the socket so a call to {#wait} can return with the exact same
419
+ # instance of {Socket}, and it also makes sure the socket won't get
420
+ # GC'd.
421
+ # @param socket [Socket, Actor] the socket instance to remember
422
+ # @return [void]
423
+ def remember_socket(socket)
424
+ @sockets[socket.to_ptr.to_i] = socket
425
+ end
426
+
427
+ # Forgets the socket because it has been removed from the poller.
428
+ # @param socket [Socket, Actor] the socket instance to forget
429
+ # @return [void]
430
+ def forget_socket(socket)
431
+ @sockets.delete(socket.to_ptr.to_i)
432
+ end
433
+
434
+ # Gets the previously remembered socket associated to the given pointer.
435
+ # @param ptr [FFI::Pointer] the pointer to a socket
436
+ # @return [Socket, Actor] the socket associated to the given pointer
437
+ # @raise [SystemCallError] if no socket is registered under given pointer
438
+ def socket_by_ptr(ptr)
439
+ @sockets[ptr.to_i] or
440
+ # NOTE: This should never happen, since #wait will return nil if
441
+ # +zpoller_wait+ returned NULL. But it's better to fail early in case
442
+ # it ever returns a wrong pointer.
443
+ raise_zmq_err("no socket known for pointer #{ptr.inspect}")
334
444
  end
335
445
  end
336
446
  end
data/lib/cztop/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module CZTop
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/cztop/z85.rb CHANGED
@@ -43,7 +43,7 @@ module CZTop
43
43
 
44
44
  def initialize
45
45
  attach_ffi_delegate(CZMQ::FFI::Zarmour.new)
46
- ffi_delegate.set_mode(:mode_z85)
46
+ ffi_delegate.set_mode(CZMQ::FFI::Zarmour::MODE_Z85)
47
47
  end
48
48
 
49
49
  # Encodes to Z85.
@@ -70,32 +70,10 @@ module CZTop
70
70
  # @raise [SystemCallError] if this fails
71
71
  def decode(input)
72
72
  raise ArgumentError, "wrong input length" if input.bytesize % 5 > 0
73
- FFI::MemoryPointer.new(:size_t) do |size_ptr|
74
- buffer_ptr = ffi_delegate.decode(input, size_ptr)
75
- raise_zmq_err if buffer_ptr.null?
76
- decoded_string = buffer_ptr.read_string(_size(size_ptr) - 1)
77
- return decoded_string
78
- end
79
- end
80
-
81
- private
82
-
83
- # Gets correct size, depending on the platform.
84
- # @return [Integer]
85
- # @see https://github.com/ffi/ffi/issues/398
86
- # @see https://github.com/ffi/ffi/issues/333
87
- def _size(size_ptr)
88
- if RUBY_ENGINE == "jruby"
89
- # NOTE: JRuby FFI doesn't have #read_uint64, nor does it have
90
- # Pointer::SIZE
91
- return size_ptr.read_ulong_long
92
- end
93
-
94
- if ::FFI::Pointer::SIZE == 8 # 64 bit
95
- size_ptr.read_uint64
96
- else
97
- size_ptr.read_uint32
98
- end
73
+ zchunk = ffi_delegate.decode(input)
74
+ raise_zmq_err if zchunk.null?
75
+ decoded_string = zchunk.data.read_string(zchunk.size - 1)
76
+ return decoded_string
99
77
  end
100
78
  end
101
79
  end
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.2.1
4
+ version: 0.3.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-01-31 00:00:00.000000000 Z
11
+ date: 2016-04-13 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.7.0
19
+ version: 0.8.3
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.7.0
26
+ version: 0.8.3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement