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 +4 -4
- data/.travis.yml +0 -8
- data/CHANGES.md +8 -0
- data/Gemfile +1 -0
- data/README.md +9 -29
- data/cztop.gemspec +1 -1
- data/examples/taxi_system/broker.rb +1 -2
- data/examples/taxi_system/client.rb +1 -1
- data/lib/cztop/monitor.rb +0 -2
- data/lib/cztop/poller.rb +361 -251
- data/lib/cztop/version.rb +1 -1
- data/lib/cztop/z85.rb +5 -27
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1619d296889181d095f1d88cf2fbf9b3f1d99f9c
|
4
|
+
data.tar.gz: c85c0ac08700a9f7558a9936acf8397688138248
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
136
|
-
* ZMQ >= 4.
|
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.
|
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.
|
42
|
+
socket = poller.simple_wait
|
44
43
|
puts "socket is readable"
|
45
44
|
msg = socket.receive
|
46
45
|
puts "got message"
|
data/lib/cztop/monitor.rb
CHANGED
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
|
5
|
-
#
|
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
|
-
@
|
38
|
-
@
|
39
|
-
@
|
40
|
-
@
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
#
|
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
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
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
|
94
|
-
|
95
|
-
|
96
|
-
|
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 [
|
72
|
+
# @return [Event] the first event of interest
|
105
73
|
# @return [nil] if the timeout expired or
|
106
|
-
# @raise [
|
74
|
+
# @raise [SystemCallError] if this failed
|
107
75
|
def wait(timeout = -1)
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
115
|
-
|
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
|
-
|
118
|
-
#
|
119
|
-
|
120
|
-
|
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>]
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
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
|
-
#
|
142
|
-
# @return [
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
154
|
-
@
|
131
|
+
def remember_socket(socket, events)
|
132
|
+
@sockets[ptr_for_socket(socket).to_i] = socket
|
133
|
+
@events[socket] = events
|
134
|
+
end
|
155
135
|
|
156
|
-
|
136
|
+
def forget_socket(socket)
|
137
|
+
@sockets.delete(ptr_for_socket(socket).to_i)
|
138
|
+
@events.delete(socket)
|
157
139
|
end
|
158
140
|
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
#
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
173
|
-
|
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
|
-
|
176
|
-
|
177
|
-
POLLOUT = 2
|
178
|
-
POLLERR = 4
|
191
|
+
# @return [CZTop::Poller.new] the associated (regular) poller
|
192
|
+
attr_reader :poller
|
179
193
|
|
180
|
-
|
181
|
-
|
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
|
-
|
187
|
-
|
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
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
201
|
-
|
202
|
-
:events, :short,
|
203
|
-
:revents, :short
|
226
|
+
if event = @poller.wait(timeout)
|
227
|
+
extract(event)
|
204
228
|
|
205
|
-
#
|
206
|
-
|
207
|
-
(
|
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
|
-
|
211
|
-
|
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
|
-
|
225
|
-
|
226
|
-
#
|
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
|
-
# @
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
248
|
-
|
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
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
-
#
|
294
|
-
|
295
|
-
|
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
|
-
|
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
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
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
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
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
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
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
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(
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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.
|
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-
|
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.
|
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.
|
26
|
+
version: 0.8.3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|