cztop 0.14.1 → 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/stable_api.yml +26 -0
- data/.rubocop.yml +175 -0
- data/CHANGES.md +9 -4
- data/Gemfile +3 -7
- data/README.md +19 -58
- data/ci/install-libczmq +22 -0
- data/ci/install-libzmq +22 -0
- data/cztop.gemspec +12 -13
- 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 +25 -90
- data/.gitlab-ci.yml +0 -32
- data/Guardfile +0 -61
- data/Procfile +0 -3
- data/ci-scripts/install-deps +0 -8
data/lib/cztop/frame.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CZTop
|
2
4
|
# Represents a CZMQ::FFI::Zframe, a part of a message.
|
3
5
|
#
|
@@ -8,6 +10,7 @@ module CZTop
|
|
8
10
|
#
|
9
11
|
# @see http://api.zeromq.org/czmq3-0:zframe
|
10
12
|
class Frame
|
13
|
+
|
11
14
|
include HasFFIDelegate
|
12
15
|
extend CZTop::HasFFIDelegate::ClassMethods
|
13
16
|
|
@@ -18,8 +21,8 @@ module CZTop
|
|
18
21
|
self.content = content if content
|
19
22
|
end
|
20
23
|
|
21
|
-
FLAG_MORE
|
22
|
-
FLAG_REUSE
|
24
|
+
FLAG_MORE = 1
|
25
|
+
FLAG_REUSE = 2
|
23
26
|
FLAG_DONTWAIT = 4
|
24
27
|
|
25
28
|
# Send {Message} to a {Socket}/{Actor}.
|
@@ -39,14 +42,14 @@ module CZTop
|
|
39
42
|
# @raise [SystemCallError] if there was some error. In that case, the
|
40
43
|
# native counterpart still exists and this {Frame} can be reused.
|
41
44
|
def send_to(destination, more: false, reuse: false, dontwait: false)
|
42
|
-
flags
|
45
|
+
flags = 0
|
43
46
|
flags |= FLAG_MORE if more
|
44
47
|
flags |= FLAG_REUSE if reuse
|
45
48
|
flags |= FLAG_DONTWAIT if dontwait
|
46
49
|
|
47
50
|
# remember pointer, in case the zframe_t won't be destroyed
|
48
51
|
zframe_ptr = ffi_delegate.to_ptr
|
49
|
-
ret
|
52
|
+
ret = CZMQ::FFI::Zframe.send(ffi_delegate, destination, flags)
|
50
53
|
|
51
54
|
if reuse || ret == -1
|
52
55
|
# zframe_t hasn't been destroyed yet: avoid memory leak.
|
@@ -55,14 +58,13 @@ module CZTop
|
|
55
58
|
end
|
56
59
|
|
57
60
|
if ret == -1
|
58
|
-
if dontwait && FFI.errno == Errno::EAGAIN::Errno
|
59
|
-
raise IO::EAGAINWaitWritable
|
60
|
-
end
|
61
|
+
raise IO::EAGAINWaitWritable if dontwait && FFI.errno == Errno::EAGAIN::Errno
|
61
62
|
|
62
63
|
raise_zmq_err
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
67
|
+
|
66
68
|
# Receive {Frame} from a {Socket}/{Actor}.
|
67
69
|
# @note This is low-level. Consider just receiving a {Message}.
|
68
70
|
# @return [Frame]
|
@@ -70,18 +72,20 @@ module CZTop
|
|
70
72
|
from_ffi_delegate(CZMQ::FFI::Zframe.recv(source))
|
71
73
|
end
|
72
74
|
|
75
|
+
|
73
76
|
# @note This string is always binary. Use String#force_encoding if needed.
|
74
77
|
# @return [String] content as string (encoding = Encoding::BINARY)
|
75
78
|
def content
|
76
79
|
ffi_delegate.data.read_string(size)
|
77
80
|
end
|
78
|
-
|
81
|
+
alias to_s content
|
79
82
|
|
80
83
|
# @return [Boolean] if this {Frame} has zero-sized content
|
81
84
|
def empty?
|
82
85
|
size.zero?
|
83
86
|
end
|
84
87
|
|
88
|
+
|
85
89
|
# Sets new content of this {Frame}.
|
86
90
|
# @param new_content [String]
|
87
91
|
# @return [new_content]
|
@@ -92,12 +96,14 @@ module CZTop
|
|
92
96
|
# NOTE: FFI::MemoryPointer will autorelease
|
93
97
|
end
|
94
98
|
|
99
|
+
|
95
100
|
# Duplicates a frame.
|
96
101
|
# @return [Frame] new frame with same content
|
97
102
|
def dup
|
98
103
|
from_ffi_delegate(ffi_delegate.dup)
|
99
104
|
end
|
100
105
|
|
106
|
+
|
101
107
|
# @return [Boolean] if the MORE indicator is set
|
102
108
|
# @note This happens when reading a frame from a {Socket} or using
|
103
109
|
# {#more=}.
|
@@ -105,6 +111,7 @@ module CZTop
|
|
105
111
|
ffi_delegate.more == 1
|
106
112
|
end
|
107
113
|
|
114
|
+
|
108
115
|
# Sets the MORE indicator.
|
109
116
|
# @param indicator [Boolean]
|
110
117
|
# @note This is NOT used when sending frame to socket.
|
@@ -114,6 +121,7 @@ module CZTop
|
|
114
121
|
ffi_delegate.set_more(indicator ? 1 : 0)
|
115
122
|
end
|
116
123
|
|
124
|
+
|
117
125
|
# Compare to another frame.
|
118
126
|
# @param other [Frame]
|
119
127
|
# @return [Boolean] if this and the other frame have identical size and
|
@@ -151,10 +159,12 @@ module CZTop
|
|
151
159
|
def routing_id=(new_routing_id)
|
152
160
|
# need to raise manually, as FFI lacks this feature.
|
153
161
|
# @see https://github.com/ffi/ffi/issues/473
|
154
|
-
raise RangeError if new_routing_id
|
162
|
+
raise RangeError if new_routing_id.negative?
|
163
|
+
|
155
164
|
ffi_delegate.set_routing_id(new_routing_id)
|
156
165
|
end
|
157
166
|
|
167
|
+
|
158
168
|
# Gets the group (radio/dish pattern).
|
159
169
|
# @note This is only set when the frame has been read from
|
160
170
|
# a {CZTop::Socket::DISH} socket.
|
@@ -163,9 +173,11 @@ module CZTop
|
|
163
173
|
def group
|
164
174
|
group = ffi_delegate.group
|
165
175
|
return nil if group.nil? || group.empty?
|
176
|
+
|
166
177
|
group
|
167
178
|
end
|
168
179
|
|
180
|
+
|
169
181
|
# Sets a new group (radio/dish pattern).
|
170
182
|
# @note This is used when the frame is sent via a {CZTop::Socket::RADIO}
|
171
183
|
# socket.
|
@@ -174,7 +186,8 @@ module CZTop
|
|
174
186
|
# @return [new_group]
|
175
187
|
def group=(new_group)
|
176
188
|
rc = ffi_delegate.set_group(new_group)
|
177
|
-
raise_zmq_err(
|
189
|
+
raise_zmq_err(format('unable to set group to %p', new_group)) if rc == -1
|
178
190
|
end
|
191
|
+
|
179
192
|
end
|
180
193
|
end
|
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'forwardable'
|
2
4
|
require 'socket' # for SocketError
|
3
5
|
|
4
6
|
# This module is used to attach the low-level objects of classes within the
|
5
7
|
# CZMQ::FFI namespace (coming from the _czmq-ffi-gen_ gem) as delegates.
|
6
8
|
module CZTop::HasFFIDelegate
|
9
|
+
|
7
10
|
# @return [CZMQ::FFI::*] the attached delegate
|
8
11
|
attr_reader :ffi_delegate
|
9
12
|
|
@@ -12,6 +15,7 @@ module CZTop::HasFFIDelegate
|
|
12
15
|
@ffi_delegate.to_ptr
|
13
16
|
end
|
14
17
|
|
18
|
+
|
15
19
|
# Attaches an FFI delegate to the current (probably new) {CZTop} object.
|
16
20
|
# @param ffi_delegate an instance of the corresponding class in the
|
17
21
|
# CZMQ::FFI namespace
|
@@ -27,6 +31,7 @@ module CZTop::HasFFIDelegate
|
|
27
31
|
@ffi_delegate = ffi_delegate
|
28
32
|
end
|
29
33
|
|
34
|
+
|
30
35
|
# Same as the counterpart in {ClassMethods}, but usable from within an
|
31
36
|
# instance.
|
32
37
|
# @see CZTop::FFIDelegate::ClassMethods#from_ffi_delegate
|
@@ -60,8 +65,10 @@ module CZTop::HasFFIDelegate
|
|
60
65
|
end
|
61
66
|
end
|
62
67
|
|
68
|
+
|
63
69
|
# Some class methods related to FFI delegates.
|
64
70
|
module ClassMethods
|
71
|
+
|
65
72
|
include Forwardable
|
66
73
|
|
67
74
|
# Delegate specified instance method to the registered FFI delegate.
|
@@ -73,6 +80,7 @@ module CZTop::HasFFIDelegate
|
|
73
80
|
def_delegator(:@ffi_delegate, method)
|
74
81
|
end
|
75
82
|
|
83
|
+
|
76
84
|
# Allocates a new instance and attaches the FFI delegate to it. This is
|
77
85
|
# useful if you already have an FFI delegate and need to attach it to a
|
78
86
|
# fresh high-level object.
|
@@ -84,7 +92,9 @@ module CZTop::HasFFIDelegate
|
|
84
92
|
def from_ffi_delegate(ffi_delegate)
|
85
93
|
obj = allocate
|
86
94
|
obj.attach_ffi_delegate(ffi_delegate)
|
87
|
-
|
95
|
+
obj
|
88
96
|
end
|
97
|
+
|
89
98
|
end
|
99
|
+
|
90
100
|
end
|
data/lib/cztop/message/frames.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CZTop
|
2
4
|
class Message
|
3
5
|
|
@@ -7,14 +9,17 @@ module CZTop
|
|
7
9
|
frames.count
|
8
10
|
end
|
9
11
|
|
12
|
+
|
10
13
|
# Access to this {Message}'s {Frame}s.
|
11
14
|
# @return [FramesAccessor]
|
12
15
|
def frames
|
13
16
|
FramesAccessor.new(self)
|
14
17
|
end
|
15
18
|
|
19
|
+
|
16
20
|
# Used to access a {Message}'s {Frame}s.
|
17
21
|
class FramesAccessor
|
22
|
+
|
18
23
|
include Enumerable
|
19
24
|
|
20
25
|
# @param message [Message]
|
@@ -22,24 +27,29 @@ module CZTop
|
|
22
27
|
@message = message
|
23
28
|
end
|
24
29
|
|
30
|
+
|
25
31
|
# Returns the last frame of this message.
|
26
32
|
# @return [Frame] first frame of Message
|
27
33
|
# @return [nil] if there are no frames
|
28
34
|
def first
|
29
35
|
first = @message.ffi_delegate.first
|
30
36
|
return nil if first.null?
|
37
|
+
|
31
38
|
Frame.from_ffi_delegate(first)
|
32
39
|
end
|
33
40
|
|
41
|
+
|
34
42
|
# Returns the last frame of this message.
|
35
43
|
# @return [Frame] last {Frame} of {Message}
|
36
44
|
# @return [nil] if there are no frames
|
37
45
|
def last
|
38
46
|
last = @message.ffi_delegate.last
|
39
47
|
return nil if last.null?
|
48
|
+
|
40
49
|
Frame.from_ffi_delegate(last)
|
41
50
|
end
|
42
51
|
|
52
|
+
|
43
53
|
# Index access to a frame/frames of this message, just like with an
|
44
54
|
# array.
|
45
55
|
# @overload [](index)
|
@@ -56,6 +66,7 @@ module CZTop
|
|
56
66
|
end
|
57
67
|
end
|
58
68
|
|
69
|
+
|
59
70
|
# Yields all frames for this message to the given block.
|
60
71
|
# @note Not thread safe.
|
61
72
|
# @yieldparam frame [Frame]
|
@@ -63,12 +74,15 @@ module CZTop
|
|
63
74
|
def each
|
64
75
|
first = first()
|
65
76
|
return unless first
|
77
|
+
|
66
78
|
yield first
|
67
|
-
while frame = @message.ffi_delegate.next
|
79
|
+
while (frame = @message.ffi_delegate.next) && !frame.null?
|
68
80
|
yield Frame.from_ffi_delegate(frame)
|
69
81
|
end
|
70
|
-
|
82
|
+
self
|
71
83
|
end
|
84
|
+
|
72
85
|
end
|
86
|
+
|
73
87
|
end
|
74
88
|
end
|
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
|