cztop 1.0.0 → 1.1.0.pre1
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/.github/workflows/coverage.yml +20 -0
- data/.github/workflows/draft_api.yml +27 -0
- data/.github/workflows/{main.yml → stable_api.yml} +6 -6
- data/.rubocop.yml +175 -0
- data/CHANGES.md +8 -1
- data/Gemfile +5 -0
- data/README.md +3 -1
- data/ci/install-libczmq +22 -0
- data/ci/install-libzmq +22 -0
- data/cztop.gemspec +3 -2
- data/lib/cztop/actor.rb +55 -26
- data/lib/cztop/authenticator.rb +18 -9
- data/lib/cztop/beacon.rb +22 -10
- data/lib/cztop/cert_store.rb +8 -2
- data/lib/cztop/certificate.rb +47 -18
- data/lib/cztop/config/comments.rb +14 -3
- data/lib/cztop/config/serialization.rb +25 -5
- data/lib/cztop/config/traversing.rb +44 -13
- data/lib/cztop/config.rb +23 -9
- data/lib/cztop/frame.rb +23 -10
- data/lib/cztop/has_ffi_delegate.rb +11 -1
- data/lib/cztop/message/frames.rb +16 -2
- data/lib/cztop/message.rb +36 -22
- data/lib/cztop/metadata.rb +35 -24
- data/lib/cztop/monitor.rb +14 -5
- data/lib/cztop/poller/aggregated.rb +31 -15
- data/lib/cztop/poller/zmq.rb +25 -22
- data/lib/cztop/poller/zpoller.rb +18 -6
- data/lib/cztop/poller.rb +43 -18
- data/lib/cztop/polymorphic_zsock_methods.rb +6 -1
- data/lib/cztop/proxy.rb +34 -19
- data/lib/cztop/send_receive_methods.rb +5 -1
- data/lib/cztop/socket/types.rb +128 -22
- data/lib/cztop/socket.rb +23 -18
- data/lib/cztop/version.rb +5 -1
- data/lib/cztop/z85/padded.rb +12 -3
- data/lib/cztop/z85/pipe.rb +40 -17
- data/lib/cztop/z85.rb +17 -6
- data/lib/cztop/zap.rb +57 -32
- data/lib/cztop/zsock_options.rb +155 -122
- data/lib/cztop.rb +2 -1
- metadata +28 -10
- data/.ruby-version +0 -1
data/lib/cztop/message.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CZTop
|
2
4
|
# Represents a CZMQ::FFI::Zmsg.
|
3
5
|
class Message
|
6
|
+
|
4
7
|
include HasFFIDelegate
|
5
8
|
extend CZTop::HasFFIDelegate::ClassMethods
|
6
9
|
include ::CZMQ::FFI
|
@@ -12,14 +15,15 @@ module CZTop
|
|
12
15
|
def self.coerce(msg)
|
13
16
|
case msg
|
14
17
|
when Message
|
15
|
-
|
18
|
+
msg
|
16
19
|
when String, Frame, Array
|
17
|
-
|
20
|
+
new(msg)
|
18
21
|
else
|
19
|
-
raise ArgumentError,
|
22
|
+
raise ArgumentError, format('cannot coerce message: %p', msg)
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
26
|
+
|
23
27
|
# @param parts [String, Frame, Array<String>, Array<Frame>] initial parts
|
24
28
|
# of the message
|
25
29
|
def initialize(parts = nil)
|
@@ -27,11 +31,13 @@ module CZTop
|
|
27
31
|
Array(parts).each { |part| self << part } if parts
|
28
32
|
end
|
29
33
|
|
34
|
+
|
30
35
|
# @return [Boolean] if this message is empty or not
|
31
36
|
def empty?
|
32
37
|
content_size.zero?
|
33
38
|
end
|
34
39
|
|
40
|
+
|
35
41
|
# Send {Message} to a {Socket} or {Actor}.
|
36
42
|
#
|
37
43
|
# @note Do NOT use this {Message} anymore afterwards. Its native
|
@@ -56,12 +62,14 @@ module CZTop
|
|
56
62
|
#
|
57
63
|
def send_to(destination)
|
58
64
|
rc = Zmsg.send(ffi_delegate, destination)
|
59
|
-
return if rc
|
65
|
+
return if rc.zero?
|
66
|
+
|
60
67
|
raise_zmq_err
|
61
68
|
rescue Errno::EAGAIN
|
62
69
|
raise IO::EAGAINWaitWritable
|
63
70
|
end
|
64
71
|
|
72
|
+
|
65
73
|
# Receive a {Message} from a {Socket} or {Actor}.
|
66
74
|
# @param source [Socket, Actor]
|
67
75
|
# @return [Message] the newly received message
|
@@ -73,11 +81,13 @@ module CZTop
|
|
73
81
|
def self.receive_from(source)
|
74
82
|
delegate = Zmsg.recv(source)
|
75
83
|
return from_ffi_delegate(delegate) unless delegate.null?
|
84
|
+
|
76
85
|
HasFFIDelegate.raise_zmq_err
|
77
86
|
rescue Errno::EAGAIN
|
78
87
|
raise IO::EAGAINWaitReadable
|
79
88
|
end
|
80
89
|
|
90
|
+
|
81
91
|
# Append a frame to this message.
|
82
92
|
# @param frame [String, Frame] what to append
|
83
93
|
# @raise [ArgumentError] if frame has an invalid type
|
@@ -90,16 +100,17 @@ module CZTop
|
|
90
100
|
when String
|
91
101
|
# NOTE: can't use addstr because the data might be binary
|
92
102
|
mem = FFI::MemoryPointer.from_string(frame)
|
93
|
-
rc
|
103
|
+
rc = ffi_delegate.addmem(mem, mem.size - 1) # without NULL byte
|
94
104
|
when Frame
|
95
105
|
rc = ffi_delegate.append(frame.ffi_delegate)
|
96
106
|
else
|
97
|
-
raise ArgumentError,
|
107
|
+
raise ArgumentError, format('invalid frame: %p', frame)
|
98
108
|
end
|
99
|
-
raise_zmq_err unless rc
|
109
|
+
raise_zmq_err unless rc.zero?
|
100
110
|
self
|
101
111
|
end
|
102
112
|
|
113
|
+
|
103
114
|
# Prepend a frame to this message.
|
104
115
|
# @param frame [String, Frame] what to prepend
|
105
116
|
# @raise [ArgumentError] if frame has an invalid type
|
@@ -112,30 +123,34 @@ module CZTop
|
|
112
123
|
when String
|
113
124
|
# NOTE: can't use pushstr because the data might be binary
|
114
125
|
mem = FFI::MemoryPointer.from_string(frame)
|
115
|
-
rc
|
126
|
+
rc = ffi_delegate.pushmem(mem, mem.size - 1) # without NULL byte
|
116
127
|
when Frame
|
117
128
|
rc = ffi_delegate.prepend(frame.ffi_delegate)
|
118
129
|
else
|
119
|
-
raise ArgumentError,
|
130
|
+
raise ArgumentError, format('invalid frame: %p', frame)
|
120
131
|
end
|
121
|
-
raise_zmq_err unless rc
|
132
|
+
raise_zmq_err unless rc.zero?
|
122
133
|
end
|
123
134
|
|
135
|
+
|
124
136
|
# Removes first part from message and returns it as a string.
|
125
137
|
# @return [String, nil] first part, if any, or nil
|
126
138
|
def pop
|
127
139
|
# NOTE: can't use popstr because the data might be binary
|
128
140
|
ptr = ffi_delegate.pop
|
129
141
|
return nil if ptr.null?
|
142
|
+
|
130
143
|
Frame.from_ffi_delegate(ptr).to_s
|
131
144
|
end
|
132
145
|
|
146
|
+
|
133
147
|
# @return [Integer] size of this message in bytes
|
134
148
|
# @see size
|
135
149
|
def content_size
|
136
150
|
ffi_delegate.content_size
|
137
151
|
end
|
138
152
|
|
153
|
+
|
139
154
|
# Returns all frames as strings in an array. This is useful if for quick
|
140
155
|
# inspection of the message.
|
141
156
|
# @note It'll read all frames in the message and turn them into Ruby
|
@@ -143,30 +158,27 @@ module CZTop
|
|
143
158
|
# @return [Array<String>] all frames
|
144
159
|
def to_a
|
145
160
|
ffi_delegate = ffi_delegate()
|
146
|
-
frame
|
161
|
+
frame = ffi_delegate.first
|
147
162
|
return [] if frame.null?
|
148
163
|
|
149
|
-
arr
|
150
|
-
while frame = ffi_delegate.next
|
164
|
+
arr = [frame.data.read_bytes(frame.size)]
|
165
|
+
while (frame = ffi_delegate.next) && !frame.null?
|
151
166
|
arr << frame.data.read_bytes(frame.size)
|
152
167
|
end
|
153
168
|
|
154
|
-
|
169
|
+
arr
|
155
170
|
end
|
156
171
|
|
172
|
+
|
157
173
|
# Inspects this {Message}.
|
158
174
|
# @return [String] shows class, number of frames, content size, and
|
159
175
|
# content (only if it's up to 200 bytes)
|
160
176
|
def inspect
|
161
|
-
|
162
|
-
|
163
|
-
to_ptr.address,
|
164
|
-
size,
|
165
|
-
content_size,
|
166
|
-
content_size <= 500 ? to_a.inspect : "[...]"
|
167
|
-
]
|
177
|
+
format('#<%s:0x%x frames=%i content_size=%i content=%s>', self.class, to_ptr.address, size, content_size,
|
178
|
+
content_size <= 500 ? to_a.inspect : '[...]')
|
168
179
|
end
|
169
180
|
|
181
|
+
|
170
182
|
# Return a frame's content.
|
171
183
|
# @return [String] the frame's content, if it exists
|
172
184
|
# @return [nil] if frame doesn't exist at given index
|
@@ -193,8 +205,10 @@ module CZTop
|
|
193
205
|
|
194
206
|
# need to raise manually, as FFI lacks this feature.
|
195
207
|
# @see https://github.com/ffi/ffi/issues/473
|
196
|
-
raise RangeError if new_routing_id
|
208
|
+
raise RangeError if new_routing_id.negative?
|
209
|
+
|
197
210
|
ffi_delegate.set_routing_id(new_routing_id)
|
198
211
|
end
|
212
|
+
|
199
213
|
end
|
200
214
|
end
|
data/lib/cztop/metadata.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
|
3
5
|
module CZTop
|
@@ -13,12 +15,15 @@ module CZTop
|
|
13
15
|
#
|
14
16
|
# @see https://rfc.zeromq.org/spec:23/ZMTP
|
15
17
|
class Metadata
|
16
|
-
|
18
|
+
|
19
|
+
VALUE_MAXLEN = (2**31) - 1
|
20
|
+
|
17
21
|
|
18
22
|
# Raised when decoding malformed metadata.
|
19
23
|
class InvalidData < StandardError
|
20
24
|
end
|
21
25
|
|
26
|
+
|
22
27
|
# regular expression used to validate property names
|
23
28
|
NAME_REGEX = /\A[[:alnum:]_.+-]{1,255}\Z/.freeze
|
24
29
|
|
@@ -29,47 +34,49 @@ module CZTop
|
|
29
34
|
# @return [String]
|
30
35
|
def self.dump(metadata)
|
31
36
|
ic_names = Set.new
|
37
|
+
|
32
38
|
metadata.map do |k, v|
|
33
39
|
ic_name = k.to_sym.downcase
|
34
|
-
if ic_names.include?
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
40
|
+
raise ArgumentError, "property #{k.inspect}: duplicate name" if ic_names.include? ic_name
|
41
|
+
|
42
|
+
ic_names << ic_name
|
43
|
+
|
39
44
|
name = k.to_s
|
40
|
-
if NAME_REGEX !~ name
|
41
|
-
|
42
|
-
end
|
45
|
+
raise ArgumentError, "property #{k.inspect}: invalid name" if NAME_REGEX !~ name
|
46
|
+
|
43
47
|
value = v.to_s
|
44
|
-
if value.bytesize > VALUE_MAXLEN
|
45
|
-
|
46
|
-
|
47
|
-
[name.size, name, value.bytesize, value].pack("CA*NA*")
|
48
|
+
raise ArgumentError, "property #{k.inspect}: value too long" if value.bytesize > VALUE_MAXLEN
|
49
|
+
|
50
|
+
[name.size, name, value.bytesize, value].pack('CA*NA*')
|
48
51
|
end.join
|
49
52
|
end
|
50
53
|
|
54
|
+
|
51
55
|
# @param data [String, Frame, #to_s] the data representing the metadata
|
52
56
|
# @return [Hash]
|
53
57
|
def self.load(data)
|
54
58
|
properties = {}
|
55
|
-
consumed
|
59
|
+
consumed = 0
|
60
|
+
|
56
61
|
while consumed < data.bytesize # while there are bytes to read
|
57
62
|
# read property name
|
58
|
-
name_length = data.byteslice(consumed).
|
59
|
-
raise InvalidData,
|
63
|
+
name_length = data.byteslice(consumed).unpack1('C') # never nil
|
64
|
+
raise InvalidData, 'zero-length property name' if name_length.zero?
|
65
|
+
|
60
66
|
name = data.byteslice(consumed + 1, name_length)
|
61
|
-
raise InvalidData,
|
67
|
+
raise InvalidData, 'incomplete name' if name.bytesize != name_length
|
68
|
+
|
62
69
|
name_sym = name.to_sym.downcase
|
63
|
-
if properties.
|
64
|
-
|
65
|
-
end
|
70
|
+
raise InvalidData, "property #{name.inspect}: duplicate name" if properties.key?(name_sym)
|
71
|
+
|
66
72
|
consumed += 1 + name.bytesize
|
67
73
|
|
68
74
|
# read property value
|
69
|
-
value_length = data.byteslice(consumed, 4).
|
70
|
-
raise InvalidData,
|
71
|
-
value
|
72
|
-
raise InvalidData,
|
75
|
+
value_length = data.byteslice(consumed, 4).unpack1('N') or
|
76
|
+
raise InvalidData, 'incomplete length'
|
77
|
+
value = data.byteslice(consumed + 4, value_length)
|
78
|
+
raise InvalidData, 'incomplete value' if value.bytesize != value_length
|
79
|
+
|
73
80
|
consumed += 4 + value.bytesize
|
74
81
|
|
75
82
|
# remember
|
@@ -78,12 +85,14 @@ module CZTop
|
|
78
85
|
new(properties)
|
79
86
|
end
|
80
87
|
|
88
|
+
|
81
89
|
# @param properties [Hash<Symbol, String>] the properties as loaded by
|
82
90
|
# {load}
|
83
91
|
def initialize(properties)
|
84
92
|
@properties = properties
|
85
93
|
end
|
86
94
|
|
95
|
+
|
87
96
|
# Gets the value corresponding to a property name. The case of the name
|
88
97
|
# is insignificant.
|
89
98
|
# @param name [Symbol, String] the property name
|
@@ -92,9 +101,11 @@ module CZTop
|
|
92
101
|
@properties[name.to_sym.downcase]
|
93
102
|
end
|
94
103
|
|
104
|
+
|
95
105
|
# @return [Hash<Symbol, String] all properties
|
96
106
|
def to_h
|
97
107
|
@properties
|
98
108
|
end
|
109
|
+
|
99
110
|
end
|
100
111
|
end
|
data/lib/cztop/monitor.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CZTop
|
2
4
|
# CZMQ monitor. Listen for socket events.
|
3
5
|
#
|
@@ -8,11 +10,12 @@ module CZTop
|
|
8
10
|
# @see http://api.zeromq.org/czmq3-0:zmonitor
|
9
11
|
# @see http://api.zeromq.org/4-1:zmq-socket-monitor
|
10
12
|
class Monitor
|
13
|
+
|
11
14
|
include ::CZMQ::FFI
|
12
15
|
|
13
16
|
# function pointer to the +zmonitor()+ function
|
14
17
|
ZMONITOR_FPTR = ::CZMQ::FFI.ffi_libraries.each do |dl|
|
15
|
-
fptr = dl.find_function(
|
18
|
+
fptr = dl.find_function('zmonitor')
|
16
19
|
break fptr if fptr
|
17
20
|
end
|
18
21
|
raise LoadError, "couldn't find zmonitor()" if ZMONITOR_FPTR.nil?
|
@@ -31,10 +34,11 @@ module CZTop
|
|
31
34
|
@actor.terminate
|
32
35
|
end
|
33
36
|
|
37
|
+
|
34
38
|
# Enable verbose logging of commands and activity.
|
35
39
|
# @return [void]
|
36
40
|
def verbose!
|
37
|
-
@actor <<
|
41
|
+
@actor << 'VERBOSE'
|
38
42
|
end
|
39
43
|
|
40
44
|
# @return [Array<String>] types of valid events
|
@@ -55,7 +59,7 @@ module CZTop
|
|
55
59
|
HANDSHAKE_FAILED_NO_DETAIL
|
56
60
|
HANDSHAKE_FAILED_PROTOCOL
|
57
61
|
HANDSHAKE_FAILED_AUTH
|
58
|
-
]
|
62
|
+
].freeze
|
59
63
|
|
60
64
|
# Configure monitor to listen for specific events.
|
61
65
|
# @param events [String] one or more events from {EVENTS}
|
@@ -65,16 +69,18 @@ module CZTop
|
|
65
69
|
EVENTS.include?(event) or
|
66
70
|
raise ArgumentError, "invalid event: #{event.inspect}"
|
67
71
|
end
|
68
|
-
@actor << [
|
72
|
+
@actor << ['LISTEN', *events]
|
69
73
|
end
|
70
74
|
|
75
|
+
|
71
76
|
# Start the monitor. After this, you can read events using {#next}.
|
72
77
|
# @return [void]
|
73
78
|
def start
|
74
|
-
@actor <<
|
79
|
+
@actor << 'START'
|
75
80
|
@actor.wait
|
76
81
|
end
|
77
82
|
|
83
|
+
|
78
84
|
# Useful for registration in an event-loop.
|
79
85
|
# @return [Integer] the FD
|
80
86
|
# @see ZsockOptions#fd
|
@@ -82,11 +88,13 @@ module CZTop
|
|
82
88
|
@actor.fd
|
83
89
|
end
|
84
90
|
|
91
|
+
|
85
92
|
# @return [Boolean] whether there's at least one event available
|
86
93
|
def readable?
|
87
94
|
@actor.readable?
|
88
95
|
end
|
89
96
|
|
97
|
+
|
90
98
|
# Get next event. This blocks until the next event is available.
|
91
99
|
# @example
|
92
100
|
# socket = CZTop::Socket::ROUTER.new("tcp://127.0.0.1:5050")
|
@@ -112,5 +120,6 @@ module CZTop
|
|
112
120
|
def next
|
113
121
|
@actor.receive
|
114
122
|
end
|
123
|
+
|
115
124
|
end
|
116
125
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CZTop
|
2
4
|
# This is a poller which is able to provide a list of readable and a list
|
3
5
|
# of writable sockets. This is useful for when you need to process socket
|
@@ -37,31 +39,37 @@ module CZTop
|
|
37
39
|
# @return [CZTop::Poller.new] the associated (regular) poller
|
38
40
|
attr_reader :poller
|
39
41
|
|
42
|
+
|
40
43
|
# @return [Array<CZTop::Socket>] readable sockets
|
41
44
|
attr_reader :readables
|
42
45
|
|
46
|
+
|
43
47
|
# @return [Array<CZTop::Socket>] writable sockets
|
44
48
|
attr_reader :writables
|
45
49
|
|
50
|
+
|
46
51
|
extend Forwardable
|
52
|
+
|
47
53
|
def_delegators :@poller,
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
:add,
|
55
|
+
:add_reader,
|
56
|
+
:add_writer,
|
57
|
+
:modify,
|
58
|
+
:remove,
|
59
|
+
:remove_reader,
|
60
|
+
:remove_writer,
|
61
|
+
:sockets
|
62
|
+
|
56
63
|
|
57
64
|
# Initializes the aggregated poller.
|
58
65
|
# @param poller [CZTop::Poller] the wrapped poller
|
59
66
|
def initialize(poller = CZTop::Poller.new)
|
60
67
|
@readables = []
|
61
68
|
@writables = []
|
62
|
-
@poller
|
69
|
+
@poller = poller
|
63
70
|
end
|
64
71
|
|
72
|
+
|
65
73
|
# Forgets all previous event information (which sockets are
|
66
74
|
# readable/writable) and waits for events anew. After getting the first
|
67
75
|
# event, {CZTop::Poller#wait} is called again with a zero-timeout to get
|
@@ -76,26 +84,29 @@ module CZTop
|
|
76
84
|
# or -1 to wait indefinitely
|
77
85
|
# @return [Boolean] whether there have been any events
|
78
86
|
def wait(timeout = -1)
|
79
|
-
@readables
|
80
|
-
@writables
|
87
|
+
@readables = []
|
88
|
+
@writables = []
|
81
89
|
@event_masks = {}
|
82
90
|
|
83
|
-
if event = @poller.wait(timeout)
|
91
|
+
if (event = @poller.wait(timeout))
|
84
92
|
extract(event)
|
85
93
|
|
86
94
|
# get all other pending events, if any, but no more blocking
|
87
|
-
while event = @poller.wait
|
95
|
+
while (event = @poller.wait 0)
|
88
96
|
extract(event)
|
89
97
|
end
|
90
98
|
|
91
99
|
restore_event_masks
|
92
100
|
return true
|
93
101
|
end
|
94
|
-
|
102
|
+
|
103
|
+
false
|
95
104
|
end
|
96
105
|
|
106
|
+
|
97
107
|
private
|
98
108
|
|
109
|
+
|
99
110
|
# Extracts the event information, adds the socket to the correct list(s)
|
100
111
|
# and modifies the socket's event mask for the socket to not turn up
|
101
112
|
# again during the next call(s) to {CZTop::Poller#wait} within {#wait}.
|
@@ -103,24 +114,29 @@ module CZTop
|
|
103
114
|
# @param event [CZTop::Poller::Event]
|
104
115
|
# @return [void]
|
105
116
|
def extract(event)
|
106
|
-
event_mask
|
117
|
+
event_mask = poller.event_mask_for_socket(event.socket)
|
107
118
|
@event_masks[event.socket] = event_mask
|
119
|
+
|
108
120
|
if event.readable?
|
109
121
|
@readables << event.socket
|
110
122
|
event_mask &= 0xFFFF ^ CZTop::Poller::ZMQ::POLLIN
|
111
123
|
end
|
124
|
+
|
112
125
|
if event.writable?
|
113
126
|
@writables << event.socket
|
114
127
|
event_mask &= 0xFFFF ^ CZTop::Poller::ZMQ::POLLOUT
|
115
128
|
end
|
129
|
+
|
116
130
|
poller.modify(event.socket, event_mask)
|
117
131
|
end
|
118
132
|
|
133
|
+
|
119
134
|
# Restores the event mask for all registered sockets to the state they
|
120
135
|
# were before the call to {#wait}.
|
121
136
|
# @return [void]
|
122
137
|
def restore_event_masks
|
123
138
|
@event_masks.each { |socket, mask| poller.modify(socket, mask) }
|
124
139
|
end
|
140
|
+
|
125
141
|
end
|
126
142
|
end
|
data/lib/cztop/poller/zmq.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module CZTop
|
3
4
|
# CZTop's interface to the low-level +zmq_poller_*()+ functions.
|
4
5
|
module Poller::ZMQ
|
5
6
|
|
@@ -8,9 +9,9 @@ module CZTop
|
|
8
9
|
POLLERR = 4
|
9
10
|
|
10
11
|
extend ::FFI::Library
|
11
|
-
lib_name
|
12
|
-
lib_dirs
|
13
|
-
lib_dirs
|
12
|
+
lib_name = 'libzmq'
|
13
|
+
lib_dirs = ['/usr/local/lib', '/opt/local/lib', '/usr/lib64']
|
14
|
+
lib_dirs = [*ENV['LIBZMQ_PATH'].split(':'), *lib_dirs] if ENV['LIBZMQ_PATH']
|
14
15
|
lib_paths = lib_dirs.map { |path| "#{path}/#{lib_name}.#{::FFI::Platform::LIBSUFFIX}" }
|
15
16
|
ffi_lib lib_paths + [lib_name]
|
16
17
|
|
@@ -24,6 +25,7 @@ module CZTop
|
|
24
25
|
# short events;
|
25
26
|
# } zmq_poller_event_t;
|
26
27
|
class PollerEvent < FFI::Struct
|
28
|
+
|
27
29
|
layout :socket, :pointer,
|
28
30
|
:fd, :int,
|
29
31
|
:user_data, :pointer,
|
@@ -31,21 +33,23 @@ module CZTop
|
|
31
33
|
|
32
34
|
# @return [Boolean] whether the socket is readable
|
33
35
|
def readable?
|
34
|
-
(self[:events] & POLLIN)
|
36
|
+
(self[:events] & POLLIN).positive?
|
35
37
|
end
|
36
38
|
|
39
|
+
|
37
40
|
# @return [Boolean] whether the socket is writable
|
38
41
|
def writable?
|
39
|
-
(self[:events] & POLLOUT)
|
42
|
+
(self[:events] & POLLOUT).positive?
|
40
43
|
end
|
44
|
+
|
41
45
|
end
|
42
46
|
|
43
|
-
#ZMQ_EXPORT void *zmq_poller_new (void);
|
44
|
-
#ZMQ_EXPORT int zmq_poller_destroy (void **poller_p);
|
45
|
-
#ZMQ_EXPORT int zmq_poller_add (void *poller, void *socket, void *user_data, short events);
|
46
|
-
#ZMQ_EXPORT int zmq_poller_modify (void *poller, void *socket, short events);
|
47
|
-
#ZMQ_EXPORT int zmq_poller_remove (void *poller, void *socket);
|
48
|
-
#ZMQ_EXPORT int zmq_poller_wait (void *poller, zmq_poller_event_t *event, long timeout);
|
47
|
+
# ZMQ_EXPORT void *zmq_poller_new (void);
|
48
|
+
# ZMQ_EXPORT int zmq_poller_destroy (void **poller_p);
|
49
|
+
# ZMQ_EXPORT int zmq_poller_add (void *poller, void *socket, void *user_data, short events);
|
50
|
+
# ZMQ_EXPORT int zmq_poller_modify (void *poller, void *socket, short events);
|
51
|
+
# ZMQ_EXPORT int zmq_poller_remove (void *poller, void *socket);
|
52
|
+
# ZMQ_EXPORT int zmq_poller_wait (void *poller, zmq_poller_event_t *event, long timeout);
|
49
53
|
|
50
54
|
# Gracefully attaches a function. If it's not available, this creates
|
51
55
|
# a placeholder class method which, when called, simply raises
|
@@ -53,27 +57,26 @@ module CZTop
|
|
53
57
|
def self.attach_function(function_nickname, function_name, *args)
|
54
58
|
super
|
55
59
|
rescue ::FFI::NotFoundError
|
56
|
-
if $VERBOSE || $DEBUG
|
57
|
-
warn "CZTop: The ZMQ function #{function_name}() is not available. Don't use CZTop::Poller."
|
58
|
-
end
|
60
|
+
warn "CZTop: The ZMQ function #{function_name}() is not available. Don't use CZTop::Poller." if $VERBOSE || $DEBUG
|
59
61
|
define_singleton_method(function_nickname) do |*|
|
60
|
-
raise NotImplementedError,
|
62
|
+
raise NotImplementedError, 'compile ZMQ with --enable-drafts'
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
66
|
opts = {
|
65
|
-
blocking: true
|
67
|
+
blocking: true # only necessary on MRI to deal with the GIL.
|
66
68
|
}
|
67
69
|
attach_function :poller_new, :zmq_poller_new, [], :pointer, **opts
|
68
70
|
attach_function :poller_destroy, :zmq_poller_destroy,
|
69
|
-
|
71
|
+
[:pointer], :int, **opts
|
70
72
|
attach_function :poller_add, :zmq_poller_add,
|
71
|
-
|
73
|
+
%i[pointer pointer pointer short], :int, **opts
|
72
74
|
attach_function :poller_modify, :zmq_poller_modify,
|
73
|
-
|
75
|
+
%i[pointer pointer short], :int, **opts
|
74
76
|
attach_function :poller_remove, :zmq_poller_remove,
|
75
|
-
|
77
|
+
%i[pointer pointer], :int, **opts
|
76
78
|
attach_function :poller_wait, :zmq_poller_wait,
|
77
|
-
|
79
|
+
%i[pointer pointer long], :int, **opts
|
80
|
+
|
78
81
|
end
|
79
82
|
end
|