cztop 0.2.1 → 0.3.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: 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