cztop 1.0.0 → 1.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/coverage.yml +20 -0
  3. data/.github/workflows/draft_api.yml +27 -0
  4. data/.github/workflows/{main.yml → stable_api.yml} +6 -6
  5. data/.rubocop.yml +175 -0
  6. data/CHANGES.md +8 -1
  7. data/Gemfile +5 -0
  8. data/README.md +3 -1
  9. data/ci/install-libczmq +22 -0
  10. data/ci/install-libzmq +22 -0
  11. data/cztop.gemspec +3 -2
  12. data/lib/cztop/actor.rb +55 -26
  13. data/lib/cztop/authenticator.rb +18 -9
  14. data/lib/cztop/beacon.rb +22 -10
  15. data/lib/cztop/cert_store.rb +8 -2
  16. data/lib/cztop/certificate.rb +47 -18
  17. data/lib/cztop/config/comments.rb +14 -3
  18. data/lib/cztop/config/serialization.rb +25 -5
  19. data/lib/cztop/config/traversing.rb +44 -13
  20. data/lib/cztop/config.rb +23 -9
  21. data/lib/cztop/frame.rb +23 -10
  22. data/lib/cztop/has_ffi_delegate.rb +11 -1
  23. data/lib/cztop/message/frames.rb +16 -2
  24. data/lib/cztop/message.rb +36 -22
  25. data/lib/cztop/metadata.rb +35 -24
  26. data/lib/cztop/monitor.rb +14 -5
  27. data/lib/cztop/poller/aggregated.rb +31 -15
  28. data/lib/cztop/poller/zmq.rb +25 -22
  29. data/lib/cztop/poller/zpoller.rb +18 -6
  30. data/lib/cztop/poller.rb +43 -18
  31. data/lib/cztop/polymorphic_zsock_methods.rb +6 -1
  32. data/lib/cztop/proxy.rb +34 -19
  33. data/lib/cztop/send_receive_methods.rb +5 -1
  34. data/lib/cztop/socket/types.rb +128 -22
  35. data/lib/cztop/socket.rb +23 -18
  36. data/lib/cztop/version.rb +5 -1
  37. data/lib/cztop/z85/padded.rb +12 -3
  38. data/lib/cztop/z85/pipe.rb +40 -17
  39. data/lib/cztop/z85.rb +17 -6
  40. data/lib/cztop/zap.rb +57 -32
  41. data/lib/cztop/zsock_options.rb +155 -122
  42. data/lib/cztop.rb +2 -1
  43. metadata +28 -10
  44. data/.ruby-version +0 -1
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CZTop
2
4
  # This is the trivial poller based on zpoller. It only supports polling
3
5
  # for readability, but it also supports doing that on CLIENT/SERVER sockets,
@@ -5,6 +7,7 @@ module CZTop
5
7
  #
6
8
  # @see http://api.zeromq.org/czmq3-0:zpoller
7
9
  class Poller::ZPoller
10
+
8
11
  include HasFFIDelegate
9
12
  extend CZTop::HasFFIDelegate::ClassMethods
10
13
  include ::CZMQ::FFI
@@ -14,24 +17,26 @@ module CZTop
14
17
  # @param readers [Socket, Actor] any additional sockets to poll for input
15
18
  def initialize(reader, *readers)
16
19
  @sockets = {} # to keep references and return same instances
17
- ptr = Zpoller.new(reader,
18
- *readers.flat_map {|r| [ :pointer, r ] },
19
- :pointer, nil)
20
+ ptr = Zpoller.new(reader,
21
+ *readers.flat_map { |r| [:pointer, r] },
22
+ :pointer, nil)
20
23
  attach_ffi_delegate(ptr)
21
24
  remember_socket(reader)
22
25
  readers.each { |r| remember_socket(r) }
23
26
  end
24
27
 
28
+
25
29
  # Adds another reader socket to the poller.
26
30
  # @param reader [Socket, Actor] socket to poll for input
27
31
  # @return [void]
28
32
  # @raise [SystemCallError] if this fails
29
33
  def add(reader)
30
34
  rc = ffi_delegate.add(reader)
31
- raise_zmq_err("unable to add socket %p" % reader) if rc == -1
35
+ raise_zmq_err(format('unable to add socket %p', reader)) if rc == -1
32
36
  remember_socket(reader)
33
37
  end
34
38
 
39
+
35
40
  # Removes a reader socket from the poller.
36
41
  # @param reader [Socket, Actor] socket to remove
37
42
  # @return [void]
@@ -40,10 +45,11 @@ module CZTop
40
45
  # @raise [SystemCallError] if this fails for another reason
41
46
  def remove(reader)
42
47
  rc = ffi_delegate.remove(reader)
43
- raise_zmq_err("unable to remove socket %p" % reader) if rc == -1
48
+ raise_zmq_err(format('unable to remove socket %p', reader)) if rc == -1
44
49
  forget_socket(reader)
45
50
  end
46
51
 
52
+
47
53
  # Waits and returns the first socket that becomes readable.
48
54
  # @param timeout [Integer] how long to wait in ms, or 0 to avoid
49
55
  # blocking, or -1 to wait indefinitely
@@ -54,11 +60,13 @@ module CZTop
54
60
  ptr = ffi_delegate.wait(timeout)
55
61
  if ptr.null?
56
62
  raise Interrupt if ffi_delegate.terminated
63
+
57
64
  return nil
58
65
  end
59
- return socket_by_ptr(ptr)
66
+ socket_by_ptr(ptr)
60
67
  end
61
68
 
69
+
62
70
  # Tells the zpoller to ignore interrupts. By default, {#wait} will return
63
71
  # immediately if it detects an interrupt (when +zsys_interrupted+ is set
64
72
  # to something other than zero). Calling this method will supress this
@@ -68,6 +76,7 @@ module CZTop
68
76
  ffi_delegate.ignore_interrupts
69
77
  end
70
78
 
79
+
71
80
  # By default the poller stops if the process receives a SIGINT or SIGTERM
72
81
  # signal. This makes it impossible to shut-down message based architectures
73
82
  # like zactors. This method lets you switch off break handling. The default
@@ -91,6 +100,7 @@ module CZTop
91
100
  @sockets[socket.to_ptr.to_i] = socket
92
101
  end
93
102
 
103
+
94
104
  # Forgets the socket because it has been removed from the poller.
95
105
  # @param socket [Socket, Actor] the socket instance to forget
96
106
  # @return [void]
@@ -98,6 +108,7 @@ module CZTop
98
108
  @sockets.delete(socket.to_ptr.to_i)
99
109
  end
100
110
 
111
+
101
112
  # Gets the previously remembered socket associated to the given pointer.
102
113
  # @param ptr [FFI::Pointer] the pointer to a socket
103
114
  # @return [Socket, Actor] the socket associated to the given pointer
@@ -109,5 +120,6 @@ module CZTop
109
120
  # it ever returns a wrong pointer.
110
121
  raise_zmq_err("no socket known for pointer #{ptr.inspect}")
111
122
  end
123
+
112
124
  end
113
125
  end
data/lib/cztop/poller.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CZTop
2
4
  # A non-trivial socket poller.
3
5
  #
@@ -9,23 +11,25 @@ module CZTop
9
11
  # * +zpoller+ can only poll for readability
10
12
  #
11
13
  class Poller
14
+
12
15
  include ::CZMQ::FFI
13
16
 
14
17
  # @param readers [Socket, Actor] sockets to poll for input
15
18
  def initialize(*readers)
16
- @sockets = {} # needed to return the same socket objects
17
- @events = {} # event masks for each socket
19
+ @sockets = {} # needed to return the same socket objects
20
+ @events = {} # event masks for each socket
18
21
  @poller_ptr = ZMQ.poller_new
19
22
  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)
23
+ proc do
24
+ ptr_ptr = ::FFI::MemoryPointer.new :pointer
25
+ ptr_ptr.write_pointer(@poller_ptr)
26
+ ZMQ.poller_destroy(ptr_ptr)
27
+ end)
25
28
  @event_ptr = FFI::MemoryPointer.new(ZMQ::PollerEvent)
26
29
  readers.each { |r| add_reader(r) }
27
30
  end
28
31
 
32
+
29
33
  # Adds a socket to be polled for readability.
30
34
  # @param socket [Socket, Actor] the socket
31
35
  # @param events [Integer] bitwise-OR'd events you're interested in (see
@@ -34,11 +38,12 @@ module CZTop
34
38
  # @raise [ArgumentError] if it's not a socket
35
39
  def add(socket, events)
36
40
  ptr = ptr_for_socket(socket)
37
- rc = ZMQ.poller_add(@poller_ptr, ptr, nil, events)
41
+ rc = ZMQ.poller_add(@poller_ptr, ptr, nil, events)
38
42
  HasFFIDelegate.raise_zmq_err if rc == -1
39
43
  remember_socket(socket, events)
40
44
  end
41
45
 
46
+
42
47
  # Convenience method to register a socket for readability. See {#add}.
43
48
  # @param socket [Socket, Actor] the socket
44
49
  # @return [void]
@@ -46,6 +51,7 @@ module CZTop
46
51
  add(socket, ZMQ::POLLIN)
47
52
  end
48
53
 
54
+
49
55
  # Convenience method to register a socket for writability. See {#add}.
50
56
  # @param socket [Socket, Actor] the socket
51
57
  # @return [void]
@@ -53,6 +59,7 @@ module CZTop
53
59
  add(socket, ZMQ::POLLOUT)
54
60
  end
55
61
 
62
+
56
63
  # Modifies the events of interest for the given socket.
57
64
  # @param socket [Socket, Actor] the socket
58
65
  # @param events [Integer] events you're interested in (see constants in
@@ -61,11 +68,12 @@ module CZTop
61
68
  # @raise [ArgumentError] if it's not a socket
62
69
  def modify(socket, events)
63
70
  ptr = ptr_for_socket(socket)
64
- rc = ZMQ.poller_modify(@poller_ptr, ptr, events)
71
+ rc = ZMQ.poller_modify(@poller_ptr, ptr, events)
65
72
  HasFFIDelegate.raise_zmq_err if rc == -1
66
73
  remember_socket(socket, events)
67
74
  end
68
75
 
76
+
69
77
  # Removes a previously registered socket. Won't raise if you're
70
78
  # trying to remove a socket that's not registered.
71
79
  # @param socket [Socket, Actor] the socket
@@ -73,11 +81,12 @@ module CZTop
73
81
  # @raise [ArgumentError] if it's not a socket
74
82
  def remove(socket)
75
83
  ptr = ptr_for_socket(socket)
76
- rc = ZMQ.poller_remove(@poller_ptr, ptr)
84
+ rc = ZMQ.poller_remove(@poller_ptr, ptr)
77
85
  HasFFIDelegate.raise_zmq_err if rc == -1
78
86
  forget_socket(socket)
79
87
  end
80
88
 
89
+
81
90
  # Removes a reader socket that was registered for readability only.
82
91
  #
83
92
  # @param socket [Socket, Actor] the socket
@@ -88,9 +97,10 @@ module CZTop
88
97
  remove(socket)
89
98
  return
90
99
  end
91
- raise ArgumentError, "not registered for readability only: %p" % socket
100
+ raise ArgumentError, format('not registered for readability only: %p', socket)
92
101
  end
93
102
 
103
+
94
104
  # Removes a reader socket that was registered for writability only.
95
105
  #
96
106
  # @param socket [Socket, Actor] the socket
@@ -101,9 +111,10 @@ module CZTop
101
111
  remove(socket)
102
112
  return
103
113
  end
104
- raise ArgumentError, "not registered for writability only: %p" % socket
114
+ raise ArgumentError, format('not registered for writability only: %p', socket)
105
115
  end
106
116
 
117
+
107
118
  # Waits for registered sockets to become readable or writable, depending
108
119
  # on what you're interested in.
109
120
  #
@@ -116,7 +127,7 @@ module CZTop
116
127
  rc = ZMQ.poller_wait(@poller_ptr, @event_ptr, timeout)
117
128
  if rc == -1
118
129
  case CZMQ::FFI::Errors.errno
119
- # NOTE: ETIMEDOUT for backwards compatibility, although this API is
130
+ # NOTE: ETIMEDOUT for backwards compatibility, although this API is
120
131
  # still DRAFT.
121
132
  when Errno::EAGAIN::Errno, Errno::ETIMEDOUT::Errno
122
133
  return nil
@@ -124,9 +135,10 @@ module CZTop
124
135
  HasFFIDelegate.raise_zmq_err
125
136
  end
126
137
  end
127
- return Event.new(self, @event_ptr)
138
+ Event.new(self, @event_ptr)
128
139
  end
129
140
 
141
+
130
142
  # Simpler version of {#wait}, which just returns the first socket of
131
143
  # interest, if any. This is useful if you either have only reader sockets,
132
144
  # or only have writer sockets.
@@ -140,27 +152,30 @@ module CZTop
140
152
  return event.socket if event
141
153
  end
142
154
 
155
+
143
156
  # @param ptr [FFI::Pointer] pointer to the socket
144
157
  # @return [Socket, Actor] socket corresponding to given pointer
145
158
  # @raise [ArgumentError] if pointer is not known
146
159
  def socket_for_ptr(ptr)
147
160
  @sockets[ptr.to_i] or
148
- raise ArgumentError, "no socket known for pointer %p" % ptr
161
+ raise ArgumentError, format('no socket known for pointer %p', ptr)
149
162
  end
150
163
 
164
+
151
165
  # @return [Array<CZTop::Socket>] all sockets registered with this poller
152
166
  # @note The actual events registered for each sockets don't matter.
153
167
  def sockets
154
168
  @sockets.values
155
169
  end
156
170
 
171
+
157
172
  # Returns the event mask for the given, registered socket.
158
173
  # @param socket [Socket, Actor] which socket's events to return
159
174
  # @return [Integer] event mask for the given socket
160
175
  # @raise [ArgumentError] if socket is not registered
161
176
  def event_mask_for_socket(socket)
162
177
  @events[socket] or
163
- raise ArgumentError, "no event mask known for socket %p" % socket
178
+ raise ArgumentError, format('no event mask known for socket %p', socket)
164
179
  end
165
180
 
166
181
  private
@@ -170,17 +185,20 @@ module CZTop
170
185
  # @raise [ArgumentError] if argument is not a socket
171
186
  def ptr_for_socket(socket)
172
187
  raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
188
+
173
189
  Zsock.resolve(socket)
174
190
  end
175
191
 
192
+
176
193
  # Keeps a reference to the given socket, and remembers its event mask.
177
194
  # @param socket [Socket, Actor] the socket
178
195
  # @param events [Integer] the event mask
179
196
  def remember_socket(socket, events)
180
197
  @sockets[ptr_for_socket(socket).to_i] = socket
181
- @events[socket] = events
198
+ @events[socket] = events
182
199
  end
183
200
 
201
+
184
202
  # Discards the referencel to the given socket, and forgets its event mask.
185
203
  # @param socket [Socket, Actor] the socket
186
204
  def forget_socket(socket)
@@ -188,33 +206,40 @@ module CZTop
188
206
  @events.delete(socket)
189
207
  end
190
208
 
209
+
191
210
  # Represents an event returned by {CZTop::Poller#wait}. This is useful to
192
211
  # find out whether the associated socket is now readable or writable, in
193
212
  # case you're interested in both. For a simpler variant, check out
194
213
  # {CZTop::Poller#simple_wait}.
195
214
  class Event
215
+
196
216
  # @param poller [CZTop::Poller] the poller instance
197
217
  # @param event_ptr [FFI::Pointer] pointer to the memory allocated for
198
218
  # the event's data (a +zmq_poller_event_t+)
199
219
  def initialize(poller, event_ptr)
200
- @poller = poller
220
+ @poller = poller
201
221
  @poller_event = ZMQ::PollerEvent.new(event_ptr)
202
222
  end
203
223
 
224
+
204
225
  # @return [Socket, Actor] the associated socket
205
226
  def socket
206
227
  @socket ||= @poller.socket_for_ptr(@poller_event[:socket])
207
228
  end
208
229
 
230
+
209
231
  # @return [Boolean] whether it's readable
210
232
  def readable?
211
233
  @poller_event.readable?
212
234
  end
213
235
 
236
+
214
237
  # @return [Boolean] whether it's writable
215
238
  def writable?
216
239
  @poller_event.writable?
217
240
  end
241
+
218
242
  end
243
+
219
244
  end
220
245
  end
@@ -1,24 +1,29 @@
1
- module CZTop
1
+ # frozen_string_literal: true
2
2
 
3
+ module CZTop
3
4
  # These are methods that can be used on a {Socket} as well as an {Actor}.
4
5
  # @see http://api.zeromq.org/czmq3-0:zsock
5
6
  module PolymorphicZsockMethods
7
+
6
8
  # Sends a signal.
7
9
  # @param status [Integer] signal (0-255)
8
10
  def signal(status = 0)
9
11
  ::CZMQ::FFI::Zsock.signal(ffi_delegate, status)
10
12
  end
11
13
 
14
+
12
15
  # Waits for a signal.
13
16
  # @return [Integer] the received signal
14
17
  def wait
15
18
  ::CZMQ::FFI::Zsock.wait(ffi_delegate)
16
19
  end
17
20
 
21
+
18
22
  # Set socket to use unbounded pipes (HWM=0); use this in cases when you are
19
23
  # totally certain the message volume can fit in memory.
20
24
  def set_unbounded
21
25
  ::CZMQ::FFI::Zsock.set_unbounded(ffi_delegate)
22
26
  end
27
+
23
28
  end
24
29
  end
data/lib/cztop/proxy.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CZTop
2
4
  # Steerable proxy which switches messages between a frontend and a backend
3
5
  # socket.
@@ -6,11 +8,12 @@ module CZTop
6
8
  #
7
9
  # @see http://api.zeromq.org/czmq3-0:zproxy
8
10
  class Proxy
11
+
9
12
  include ::CZMQ::FFI
10
13
 
11
14
  # function pointer to the +zmonitor()+ function
12
15
  ZPROXY_FPTR = ::CZMQ::FFI.ffi_libraries.each do |dl|
13
- fptr = dl.find_function("zproxy")
16
+ fptr = dl.find_function('zproxy')
14
17
  break fptr if fptr
15
18
  end
16
19
  raise LoadError, "couldn't find zproxy()" if ZPROXY_FPTR.nil?
@@ -28,13 +31,15 @@ module CZTop
28
31
  @actor.terminate
29
32
  end
30
33
 
34
+
31
35
  # Enable verbose logging of commands and activity.
32
36
  # @return [void]
33
37
  def verbose!
34
- @actor << "VERBOSE"
38
+ @actor << 'VERBOSE'
35
39
  @actor.wait
36
40
  end
37
41
 
42
+
38
43
  # Returns a configurator object which you can use to configure the
39
44
  # frontend socket.
40
45
  # @return [Configurator] (memoized) frontend configurator
@@ -42,6 +47,7 @@ module CZTop
42
47
  @frontend ||= Configurator.new(self, :frontend)
43
48
  end
44
49
 
50
+
45
51
  # Returns a configurator object which you can use to configure the backend
46
52
  # socket.
47
53
  # @return [Configurator] (memoized) backend configurator
@@ -49,54 +55,59 @@ module CZTop
49
55
  @backend ||= Configurator.new(self, :backend)
50
56
  end
51
57
 
58
+
52
59
  # Captures all proxied messages and delivers them to a PULL socket bound
53
60
  # to the specified endpoint.
54
61
  # @note The PULL socket has to be bound before calling this method.
55
62
  # @param endpoint [String] the endpoint to which the PULL socket is bound to
56
63
  # @return [void]
57
64
  def capture(endpoint)
58
- @actor << ["CAPTURE", endpoint]
65
+ @actor << ['CAPTURE', endpoint]
59
66
  @actor.wait
60
67
  end
61
68
 
69
+
62
70
  # Pauses proxying of any messages.
63
71
  # @note This causes any messages to be queued up and potentialy hit the
64
72
  # high-water mark on the frontend or backend socket, causing messages to
65
73
  # be dropped or writing applications to block.
66
74
  # @return [void]
67
75
  def pause
68
- @actor << "PAUSE"
76
+ @actor << 'PAUSE'
69
77
  @actor.wait
70
78
  end
71
79
 
80
+
72
81
  # Resume proxying of messages.
73
82
  # @note This is only needed after a call to {#pause}, not to start the
74
83
  # proxy. Proxying starts as soon as the frontend and backend sockets are
75
84
  # properly attached.
76
85
  # @return [void]
77
86
  def resume
78
- @actor << "RESUME"
87
+ @actor << 'RESUME'
79
88
  @actor.wait
80
89
  end
81
90
 
91
+
82
92
  # Used to configure the socket on one side of a {Proxy}.
83
93
  class Configurator
94
+
84
95
  # @return [Array<Symbol>] supported socket types
85
96
  SOCKET_TYPES = %i[
86
97
  PAIR PUB SUB REQ REP
87
98
  DEALER ROUTER PULL PUSH
88
99
  XPUB XSUB
89
- ]
100
+ ].freeze
90
101
 
91
102
  # @param proxy [Proxy] the proxy instance
92
103
  # @param side [Symbol] :frontend or :backend
93
104
  def initialize(proxy, side)
94
105
  @proxy = proxy
95
- @side = case side
96
- when :frontend then "FRONTEND"
97
- when :backend then "BACKEND"
98
- else raise ArgumentError, "invalid side: #{side.inspect}"
99
- end
106
+ @side = case side
107
+ when :frontend then 'FRONTEND'
108
+ when :backend then 'BACKEND'
109
+ else raise ArgumentError, "invalid side: #{side.inspect}"
110
+ end
100
111
  end
101
112
 
102
113
  # @return [Proxy] the proxy this {Configurator} works on
@@ -111,27 +122,29 @@ module CZTop
111
122
  # @raise [ArgumentError] if the given socket type is invalid
112
123
  # @return [void]
113
124
  def bind(socket_type, endpoint)
114
- unless SOCKET_TYPES.include?(socket_type)
115
- raise ArgumentError, "invalid socket type: #{socket_type}"
116
- end
117
- @proxy.actor << [ @side, socket_type.to_s, endpoint ]
125
+ raise ArgumentError, "invalid socket type: #{socket_type}" unless SOCKET_TYPES.include?(socket_type)
126
+
127
+ @proxy.actor << [@side, socket_type.to_s, endpoint]
118
128
  @proxy.actor.wait
119
129
  end
120
130
 
131
+
121
132
  # Set ZAP domain for authentication.
122
133
  # @param domain [String] the ZAP domain
123
134
  def domain=(domain)
124
- @proxy.actor << [ "DOMAIN", @side, domain ]
135
+ @proxy.actor << ['DOMAIN', @side, domain]
125
136
  @proxy.actor.wait
126
137
  end
127
138
 
139
+
128
140
  # Configure PLAIN authentication on this socket.
129
141
  # @note You'll have to use a {CZTop::Authenticator}.
130
142
  def PLAIN_server!
131
- @proxy.actor << [ "PLAIN", @side ]
143
+ @proxy.actor << ['PLAIN', @side]
132
144
  @proxy.actor.wait
133
145
  end
134
146
 
147
+
135
148
  # Configure CURVE authentication on this socket.
136
149
  # @note You'll have to use a {CZTop::Authenticator}.
137
150
  # @param cert [Certificate] this server's certificate,
@@ -139,11 +152,13 @@ module CZTop
139
152
  def CURVE_server!(cert)
140
153
  public_key = cert.public_key
141
154
  secret_key = cert.secret_key or
142
- raise ArgumentError, "no secret key in certificate"
155
+ raise ArgumentError, 'no secret key in certificate'
143
156
 
144
- @proxy.actor << [ "CURVE", @side, public_key, secret_key ]
157
+ @proxy.actor << ['CURVE', @side, public_key, secret_key]
145
158
  @proxy.actor.wait
146
159
  end
160
+
147
161
  end
162
+
148
163
  end
149
164
  end
@@ -1,10 +1,12 @@
1
- module CZTop
1
+ # frozen_string_literal: true
2
2
 
3
+ module CZTop
3
4
  # These are methods that can be used on a {Socket} as well as an {Actor},
4
5
  # but actually just pass through to methods of {Message} (which take
5
6
  # a polymorphic reference, in Ruby as well as in C).
6
7
  # @see http://api.zeromq.org/czmq3-0:zmsg
7
8
  module SendReceiveMethods
9
+
8
10
  # Sends a message.
9
11
  #
10
12
  # @param message [Message, String, Array<parts>] the message to send
@@ -20,6 +22,7 @@ module CZTop
20
22
  self
21
23
  end
22
24
 
25
+
23
26
  # Receives a message.
24
27
  #
25
28
  # @return [Message]
@@ -31,5 +34,6 @@ module CZTop
31
34
  def receive
32
35
  Message.receive_from(self)
33
36
  end
37
+
34
38
  end
35
39
  end