zmqmachine 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.bnsignore +20 -0
- data/History.txt +21 -0
- data/README.rdoc +85 -0
- data/Rakefile +21 -0
- data/examples/fake_ftp.rb +242 -0
- data/examples/one_handed_ping_pong.rb +74 -0
- data/examples/ping_pong.rb +86 -0
- data/examples/pub_sub.rb +195 -0
- data/examples/throttled_ping_pong.rb +103 -0
- data/lib/zm/address.rb +70 -0
- data/lib/zm/deferrable.rb +209 -0
- data/lib/zm/exceptions.rb +45 -0
- data/lib/zm/message.rb +52 -0
- data/lib/zm/reactor.rb +458 -0
- data/lib/zm/sockets/base.rb +216 -0
- data/lib/zm/sockets/pair.rb +87 -0
- data/lib/zm/sockets/pub.rb +80 -0
- data/lib/zm/sockets/rep.rb +96 -0
- data/lib/zm/sockets/req.rb +96 -0
- data/lib/zm/sockets/sub.rb +83 -0
- data/lib/zm/sockets/xrep.rb +86 -0
- data/lib/zm/sockets/xreq.rb +86 -0
- data/lib/zm/sockets.rb +4 -0
- data/lib/zm/timers.rb +220 -0
- data/lib/zmqmachine.rb +76 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/zmqmachine_spec.rb +6 -0
- data/version.txt +1 -0
- data/zmqmachine.gemspec +48 -0
- metadata +133 -0
data/lib/zm/reactor.rb
ADDED
@@ -0,0 +1,458 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# Author:: Chuck Remes
|
4
|
+
# Homepage:: http://github.com/chuckremes/zmqmachine
|
5
|
+
# Date:: 20100602
|
6
|
+
#
|
7
|
+
#----------------------------------------------------------------------------
|
8
|
+
#
|
9
|
+
# Copyright (C) 2010 by Chuck Remes. All Rights Reserved.
|
10
|
+
# Email: cremes at mac dot com
|
11
|
+
#
|
12
|
+
# (The MIT License)
|
13
|
+
#
|
14
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
15
|
+
# a copy of this software and associated documentation files (the
|
16
|
+
# 'Software'), to deal in the Software without restriction, including
|
17
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
18
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
19
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
20
|
+
# the following conditions:
|
21
|
+
#
|
22
|
+
# The above copyright notice and this permission notice shall be
|
23
|
+
# included in all copies or substantial portions of the Software.
|
24
|
+
#
|
25
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
26
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
27
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
28
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
29
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
30
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
31
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
32
|
+
#
|
33
|
+
#---------------------------------------------------------------------------
|
34
|
+
#
|
35
|
+
#
|
36
|
+
|
37
|
+
module ZMQMachine
|
38
|
+
|
39
|
+
class Reactor
|
40
|
+
attr_reader :name
|
41
|
+
|
42
|
+
# +poll_interval+ is the number of milliseconds to block while
|
43
|
+
# waiting for new 0mq socket events; default is 0
|
44
|
+
#
|
45
|
+
def initialize name, poll_interval = 0
|
46
|
+
@name = name
|
47
|
+
@running = false
|
48
|
+
@thread = nil
|
49
|
+
@poll_interval = determine_interval poll_interval
|
50
|
+
@timers = ZMQMachine::Timers.new
|
51
|
+
|
52
|
+
@proc_queue = []
|
53
|
+
@proc_queue_mutex = Mutex.new
|
54
|
+
|
55
|
+
# could raise if it fails
|
56
|
+
@context = ZMQ::Context.new 1
|
57
|
+
@poller = ZMQ::Poller.new
|
58
|
+
@sockets = []
|
59
|
+
@raw_to_socket = {}
|
60
|
+
Thread.abort_on_exception = true
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns true when the reactor is running OR while it is in the
|
64
|
+
# midst of a shutdown request.
|
65
|
+
#
|
66
|
+
# Returns false when the reactor thread does not exist.
|
67
|
+
#
|
68
|
+
def running?() @running; end
|
69
|
+
|
70
|
+
# The main entry point for all new reactor contexts. This proc
|
71
|
+
# or block given to this method is evaluated *once* before
|
72
|
+
# entering the reactor loop. This evaluation generally sets up
|
73
|
+
# sockets and timers that will do the real work once the loop
|
74
|
+
# is executed.
|
75
|
+
#
|
76
|
+
def run blk = nil, &block
|
77
|
+
blk ||= block
|
78
|
+
@running, @stopping = true, false
|
79
|
+
|
80
|
+
@thread = Thread.new do
|
81
|
+
blk.call self if blk
|
82
|
+
|
83
|
+
while !@stopping && @running do
|
84
|
+
run_once
|
85
|
+
end
|
86
|
+
|
87
|
+
cleanup
|
88
|
+
end
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Marks the reactor as eligible for termination. Then waits for the
|
93
|
+
# reactor thread to exit via #join (no timeout).
|
94
|
+
#
|
95
|
+
# The reactor is not forcibly terminated if it is currently blocked
|
96
|
+
# by some long-running operation. Use #kill to forcibly terminate
|
97
|
+
# the reactor.
|
98
|
+
#
|
99
|
+
def stop
|
100
|
+
# wait until the thread loops around again and exits on its own
|
101
|
+
@stopping = true
|
102
|
+
join
|
103
|
+
end
|
104
|
+
|
105
|
+
# Join on the thread running this reactor instance. Default behavior
|
106
|
+
# is to wait indefinitely for the thread to exit.
|
107
|
+
#
|
108
|
+
# Pass an optional +delay+ value measured in milliseconds; the
|
109
|
+
# thread will be stopped if it hasn't exited by the end of +delay+
|
110
|
+
# milliseconds.
|
111
|
+
#
|
112
|
+
# Returns immediately when the thread has already exited.
|
113
|
+
#
|
114
|
+
def join delay = nil
|
115
|
+
# don't allow the thread to try and join itself and only worry about
|
116
|
+
# joining for live threads
|
117
|
+
if @thread.alive? && @thread != Thread.current
|
118
|
+
if delay
|
119
|
+
# convert to seconds to meet the argument expectations of Thread#join
|
120
|
+
seconds = delay / 1000.0
|
121
|
+
@thread.join seconds
|
122
|
+
else
|
123
|
+
@thread.join
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Kills the running reactor instance by terminating its thread.
|
129
|
+
#
|
130
|
+
# After the thread exits, the reactor attempts to clean up after itself
|
131
|
+
# and kill any pending I/O.
|
132
|
+
#
|
133
|
+
def kill
|
134
|
+
@stopping = true
|
135
|
+
@thread.kill
|
136
|
+
cleanup
|
137
|
+
end
|
138
|
+
|
139
|
+
# Schedules a proc or block to execute on the next trip through the
|
140
|
+
# reactor loop.
|
141
|
+
#
|
142
|
+
# This method is thread-safe.
|
143
|
+
#
|
144
|
+
def next_tick blk = nil, &block
|
145
|
+
blk ||= block
|
146
|
+
@proc_queue_mutex.synchronize do
|
147
|
+
@proc_queue << blk
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Removes the given +sock+ socket from the reactor context. It is deregistered
|
152
|
+
# for new events and closed. Any queued messages are silently dropped.
|
153
|
+
#
|
154
|
+
def close_socket sock
|
155
|
+
delete_socket sock
|
156
|
+
sock.raw_socket.close
|
157
|
+
end
|
158
|
+
|
159
|
+
# Creates a REQ socket and attaches +handler_instance+ to the
|
160
|
+
# resulting socket. Should only be paired with one other
|
161
|
+
# #rep_socket instance.
|
162
|
+
#
|
163
|
+
# +handler_instance+ must implement the #on_writable and
|
164
|
+
# #on_writable_error methods. The reactor will call those methods
|
165
|
+
# based upon new events.
|
166
|
+
#
|
167
|
+
# All handlers must implement the #on_attach method.
|
168
|
+
#
|
169
|
+
def req_socket handler_instance
|
170
|
+
sock = ZMQMachine::Socket::Req.new @context, handler_instance
|
171
|
+
save_socket sock
|
172
|
+
sock
|
173
|
+
end
|
174
|
+
|
175
|
+
# Creates a REP socket and attaches +handler_instance+ to the
|
176
|
+
# resulting socket. Should only be paired with one other
|
177
|
+
# #req_socket instance.
|
178
|
+
#
|
179
|
+
# +handler_instance+ must implement the #on_readable and
|
180
|
+
# #on_readable_error methods. The reactor will call those methods
|
181
|
+
# based upon new events.
|
182
|
+
#
|
183
|
+
# All handlers must implement the #on_attach method.
|
184
|
+
#
|
185
|
+
def rep_socket handler_instance
|
186
|
+
sock = ZMQMachine::Socket::Rep.new @context, handler_instance
|
187
|
+
save_socket sock
|
188
|
+
sock
|
189
|
+
end
|
190
|
+
|
191
|
+
# Creates a XREQ socket and attaches +handler_instance+ to the
|
192
|
+
# resulting socket. Should only be paired with one other
|
193
|
+
# #rep_socket instance.
|
194
|
+
#
|
195
|
+
# +handler_instance+ must implement the #on_readable,
|
196
|
+
# #on_readable_error, #on_writable and #on_writable_error
|
197
|
+
# methods. The reactor will call those methods
|
198
|
+
# based upon new events.
|
199
|
+
#
|
200
|
+
# All handlers must implement the #on_attach method.
|
201
|
+
#
|
202
|
+
def xreq_socket handler_instance
|
203
|
+
sock = ZMQMachine::Socket::XReq.new @context, handler_instance
|
204
|
+
save_socket sock
|
205
|
+
sock
|
206
|
+
end
|
207
|
+
|
208
|
+
# Creates a XREP socket and attaches +handler_instance+ to the
|
209
|
+
# resulting socket. Should only be paired with one other
|
210
|
+
# #req_socket instance.
|
211
|
+
#
|
212
|
+
# +handler_instance+ must implement the #on_readable,
|
213
|
+
# #on_readable_error, #on_writable and #on_writable_error
|
214
|
+
# methods. The reactor will call those methods
|
215
|
+
# based upon new events.
|
216
|
+
#
|
217
|
+
# All handlers must implement the #on_attach method.
|
218
|
+
#
|
219
|
+
def xrep_socket handler_instance
|
220
|
+
sock = ZMQMachine::Socket::XRep.new @context, handler_instance
|
221
|
+
save_socket sock
|
222
|
+
sock
|
223
|
+
end
|
224
|
+
|
225
|
+
# Creates a PAIR socket and attaches +handler_instance+ to the
|
226
|
+
# resulting socket. Works only with other #pair_socket instances
|
227
|
+
# in the same or other reactor instance.
|
228
|
+
#
|
229
|
+
# +handler_instance+ must implement the #on_readable and
|
230
|
+
# #on_readable_error methods. Each handler must also implement
|
231
|
+
# the #on_writable and #on_writable_error methods.
|
232
|
+
# The reactor will call those methods
|
233
|
+
# based upon new events.
|
234
|
+
#
|
235
|
+
# All handlers must implement the #on_attach method.
|
236
|
+
#
|
237
|
+
def pair_socket handler_instance
|
238
|
+
sock = ZMQMachine::Socket::Pair.new @context, handler_instance
|
239
|
+
save_socket sock
|
240
|
+
sock
|
241
|
+
end
|
242
|
+
|
243
|
+
# Creates a PUB socket and attaches +handler_instance+ to the
|
244
|
+
# resulting socket. Usually paired with one or more
|
245
|
+
# #sub_socket instances in the same or other reactor instance.
|
246
|
+
#
|
247
|
+
# +handler_instance+ must implement the #on_writable and
|
248
|
+
# #on_writable_error methods. The reactor will call those methods
|
249
|
+
# based upon new events. This socket type can *only* write; it
|
250
|
+
# can never receive/read messages.
|
251
|
+
#
|
252
|
+
# All handlers must implement the #on_attach method.
|
253
|
+
#
|
254
|
+
def pub_socket handler_instance
|
255
|
+
sock = ZMQMachine::Socket::Pub.new @context, handler_instance
|
256
|
+
save_socket sock
|
257
|
+
sock
|
258
|
+
end
|
259
|
+
|
260
|
+
# Creates a SUB socket and attaches +handler_instance+ to the
|
261
|
+
# resulting socket. Usually paired with one or more
|
262
|
+
# #pub_socket in the same or different reactor context.
|
263
|
+
#
|
264
|
+
# +handler_instance+ must implement the #on_readable and
|
265
|
+
# #on_readable_error methods. The reactor will call those methods
|
266
|
+
# based upon new events. This socket type can *only* read; it
|
267
|
+
# can never write/send messages.
|
268
|
+
#
|
269
|
+
# All handlers must implement the #on_attach method.
|
270
|
+
#
|
271
|
+
def sub_socket handler_instance
|
272
|
+
sock = ZMQMachine::Socket::Sub.new @context, handler_instance
|
273
|
+
save_socket sock
|
274
|
+
sock
|
275
|
+
end
|
276
|
+
|
277
|
+
# Registers the +sock+ for POLLOUT events that will cause the
|
278
|
+
# reactor to call the handler's on_writable method.
|
279
|
+
#
|
280
|
+
def register_writable sock
|
281
|
+
@poller.register_writable sock.raw_socket
|
282
|
+
end
|
283
|
+
|
284
|
+
# Deregisters the +sock+ for POLLOUT. The handler will no longer
|
285
|
+
# receive calls to on_writable.
|
286
|
+
#
|
287
|
+
def deregister_writable sock
|
288
|
+
@poller.deregister_writable sock.raw_socket
|
289
|
+
end
|
290
|
+
|
291
|
+
# Registers the +sock+ for POLLIN events that will cause the
|
292
|
+
# reactor to call the handler's on_readable method.
|
293
|
+
#
|
294
|
+
def register_readable sock
|
295
|
+
@poller.register_readable sock.raw_socket
|
296
|
+
end
|
297
|
+
|
298
|
+
# Deregisters the +sock+ for POLLIN events. The handler will no longer
|
299
|
+
# receive calls to on_readable.
|
300
|
+
#
|
301
|
+
def deregister_readable sock
|
302
|
+
@poller.deregister_readable sock.raw_socket
|
303
|
+
end
|
304
|
+
|
305
|
+
# Creates a timer that will fire a single time. Expects either a
|
306
|
+
# +timer_proc+ proc or a block, otherwise no timer is created.
|
307
|
+
#
|
308
|
+
# +delay+ is measured in milliseconds (1 second equals 1000
|
309
|
+
# milliseconds)
|
310
|
+
#
|
311
|
+
def oneshot_timer delay, timer_proc = nil, &blk
|
312
|
+
blk ||= timer_proc
|
313
|
+
@timers.add_oneshot delay, blk
|
314
|
+
end
|
315
|
+
|
316
|
+
# Creates a timer that will fire every +delay+ milliseconds until
|
317
|
+
# it is explicitly cancelled. Expects either a +timer_proc+ proc
|
318
|
+
# or a block, otherwise no timer is created.
|
319
|
+
#
|
320
|
+
# +delay+ is measured in milliseconds (1 second equals 1000
|
321
|
+
# milliseconds)
|
322
|
+
#
|
323
|
+
def periodical_timer delay, timer_proc = nil, &blk
|
324
|
+
blk ||= timer_proc
|
325
|
+
@timers.add_periodical delay, blk
|
326
|
+
end
|
327
|
+
|
328
|
+
# Cancels an existing timer if it hasn't already fired.
|
329
|
+
#
|
330
|
+
# Returns true if cancelled, false if otherwise.
|
331
|
+
#
|
332
|
+
def cancel_timer timer
|
333
|
+
@timers.cancel timer
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
private
|
338
|
+
|
339
|
+
def run_once
|
340
|
+
run_procs
|
341
|
+
run_timers
|
342
|
+
poll
|
343
|
+
end
|
344
|
+
|
345
|
+
# Close each open socket and terminate the reactor context; this will
|
346
|
+
# release the native memory backing each of these objects
|
347
|
+
def cleanup
|
348
|
+
# work on a dup since #close_socket deletes from @sockets
|
349
|
+
@sockets.dup.each { |sock| close_socket sock }
|
350
|
+
@context.terminate
|
351
|
+
@running = false
|
352
|
+
end
|
353
|
+
|
354
|
+
def run_timers
|
355
|
+
@timers.fire_expired
|
356
|
+
end
|
357
|
+
|
358
|
+
# work on a copy of the queue; some procs may reschedule themselves to
|
359
|
+
# run again immediately, so by using a copy we make them wait until the next
|
360
|
+
# loop
|
361
|
+
def run_procs
|
362
|
+
work = nil
|
363
|
+
@proc_queue_mutex.synchronize do
|
364
|
+
work, @proc_queue = @proc_queue, []
|
365
|
+
end
|
366
|
+
|
367
|
+
until work.empty? do
|
368
|
+
work.pop.call
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def poll
|
373
|
+
@poller.poll @poll_interval
|
374
|
+
|
375
|
+
@poller.readables.each { |sock| @raw_to_socket[sock].resume_read }
|
376
|
+
@poller.writables.each { |sock| @raw_to_socket[sock].resume_write }
|
377
|
+
end
|
378
|
+
|
379
|
+
def save_socket sock
|
380
|
+
@poller.register sock.raw_socket, sock.poll_options
|
381
|
+
@sockets << sock
|
382
|
+
@raw_to_socket[sock.raw_socket] = sock
|
383
|
+
end
|
384
|
+
|
385
|
+
def delete_socket sock
|
386
|
+
@poller.delete sock.raw_socket
|
387
|
+
@sockets.delete sock
|
388
|
+
@raw_to_socket.delete sock.raw_socket
|
389
|
+
end
|
390
|
+
|
391
|
+
|
392
|
+
# Internally converts the number to microseconds
|
393
|
+
def determine_interval interval
|
394
|
+
# set a lower bound of 100 usec so we don't burn up the CPU
|
395
|
+
interval <= 0 ? 100 : (interval * 1000).to_i
|
396
|
+
end
|
397
|
+
|
398
|
+
end # class Reactor
|
399
|
+
|
400
|
+
|
401
|
+
# Not implemented. Just a (broken) thought experiment at the moment.
|
402
|
+
#
|
403
|
+
class SyncReactor
|
404
|
+
|
405
|
+
def initialize name
|
406
|
+
@klass_path = ZMQMachine::Sync
|
407
|
+
@poll_interval = 10
|
408
|
+
@active = true
|
409
|
+
super name
|
410
|
+
end
|
411
|
+
|
412
|
+
def run blk = nil, &block
|
413
|
+
blk ||= block
|
414
|
+
@running = true
|
415
|
+
|
416
|
+
@thread = Thread.new do
|
417
|
+
@fiber = Fiber.new { |context| blk.call context }
|
418
|
+
func = Proc.new { |context| @fiber.resume context }
|
419
|
+
|
420
|
+
run_once func
|
421
|
+
while @running do
|
422
|
+
run_once
|
423
|
+
end
|
424
|
+
|
425
|
+
cleanup
|
426
|
+
end
|
427
|
+
|
428
|
+
self
|
429
|
+
end
|
430
|
+
|
431
|
+
def deactivate() @active = false; end
|
432
|
+
|
433
|
+
def active?() @active; end
|
434
|
+
|
435
|
+
def while_active? &blk
|
436
|
+
begin
|
437
|
+
while @active do
|
438
|
+
yield
|
439
|
+
end
|
440
|
+
rescue => e
|
441
|
+
p e
|
442
|
+
end
|
443
|
+
|
444
|
+
@running = false
|
445
|
+
end
|
446
|
+
|
447
|
+
|
448
|
+
private
|
449
|
+
|
450
|
+
def poll
|
451
|
+
@poller.poll @poll_interval
|
452
|
+
|
453
|
+
@poller.writables.each { |sock| sock.resume }
|
454
|
+
@poller.readables.each { |sock| sock.resume }
|
455
|
+
end
|
456
|
+
end # class SyncReactor
|
457
|
+
|
458
|
+
end # module ZMQMachine
|
@@ -0,0 +1,216 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# Author:: Chuck Remes
|
4
|
+
# Homepage:: http://github.com/chuckremes/zmqmachine
|
5
|
+
# Date:: 20100602
|
6
|
+
#
|
7
|
+
#----------------------------------------------------------------------------
|
8
|
+
#
|
9
|
+
# Copyright (C) 2010 by Chuck Remes. All Rights Reserved.
|
10
|
+
# Email: cremes at mac dot com
|
11
|
+
#
|
12
|
+
# (The MIT License)
|
13
|
+
#
|
14
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
15
|
+
# a copy of this software and associated documentation files (the
|
16
|
+
# 'Software'), to deal in the Software without restriction, including
|
17
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
18
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
19
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
20
|
+
# the following conditions:
|
21
|
+
#
|
22
|
+
# The above copyright notice and this permission notice shall be
|
23
|
+
# included in all copies or substantial portions of the Software.
|
24
|
+
#
|
25
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
26
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
27
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
28
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
29
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
30
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
31
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
32
|
+
#
|
33
|
+
#---------------------------------------------------------------------------
|
34
|
+
#
|
35
|
+
#
|
36
|
+
|
37
|
+
module ZMQMachine
|
38
|
+
|
39
|
+
module Socket
|
40
|
+
|
41
|
+
module Base
|
42
|
+
attr_reader :raw_socket, :kind
|
43
|
+
attr_reader :poll_options
|
44
|
+
|
45
|
+
def initialize context, handler
|
46
|
+
@state = :init
|
47
|
+
@context = context
|
48
|
+
@bindings = []
|
49
|
+
@connections = []
|
50
|
+
|
51
|
+
@handler = handler
|
52
|
+
@raw_socket = allocate_socket @context
|
53
|
+
attach @handler
|
54
|
+
end
|
55
|
+
|
56
|
+
# Call the handler's #on_attach method and pass itself
|
57
|
+
# so the handler may complete its setup.
|
58
|
+
#
|
59
|
+
# The #on_attach method is passed a single argument named
|
60
|
+
# +socket+. The method should probably #bind or #connect
|
61
|
+
# to an address and potentially schedule (via timer) an
|
62
|
+
# operation or begin sending messages immediately.
|
63
|
+
#
|
64
|
+
def attach handler
|
65
|
+
raise ArgumentError, "Handler must provide an 'on_attach' method" unless handler.respond_to? :on_attach
|
66
|
+
handler.on_attach self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Creates a 0mq socket endpoint for the transport given in the
|
70
|
+
# +address+. Other 0mq sockets may then #connect to this bound
|
71
|
+
# endpoint.
|
72
|
+
#
|
73
|
+
def bind address
|
74
|
+
begin
|
75
|
+
@bindings << address
|
76
|
+
@raw_socket.bind address.to_s
|
77
|
+
true
|
78
|
+
rescue ZMQ::ZeroMQError
|
79
|
+
@bindings.pop
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Connect this 0mq socket to the 0mq socket bound to the endpoint
|
85
|
+
# described by the +address+.
|
86
|
+
#
|
87
|
+
def connect address
|
88
|
+
begin
|
89
|
+
@connections << address
|
90
|
+
@raw_socket.connect address.to_s
|
91
|
+
true
|
92
|
+
rescue ZMQ::ZeroMQError
|
93
|
+
@connections.pop
|
94
|
+
false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Called to send a ZMQ::Message that was populated with data.
|
99
|
+
#
|
100
|
+
# Returns true on success, false otherwise.
|
101
|
+
#
|
102
|
+
# May raise a ZMQ::SocketError.
|
103
|
+
#
|
104
|
+
def send_message message, multipart = false
|
105
|
+
queued = @raw_socket.send message, ZMQ::NOBLOCK | (multipart ? ZMQ::SNDMORE : 0)
|
106
|
+
queued
|
107
|
+
end
|
108
|
+
|
109
|
+
# Convenience method to send a string on the socket. It handles
|
110
|
+
# the creation of a ZMQ::Message and populates it appropriately.
|
111
|
+
#
|
112
|
+
# Returns true on success, false otherwise.
|
113
|
+
#
|
114
|
+
# May raise a ZMQ::SocketError.
|
115
|
+
#
|
116
|
+
def send_message_string message
|
117
|
+
queued = @raw_socket.send_string message, ZMQ::NOBLOCK
|
118
|
+
queued
|
119
|
+
end
|
120
|
+
|
121
|
+
# Convenience method for sending a multi-part message. The
|
122
|
+
# +messages+ argument must respond to :size and :at (like
|
123
|
+
# an Array).
|
124
|
+
#
|
125
|
+
# May raise a ZMQ::SocketError.
|
126
|
+
#
|
127
|
+
def send_messages messages
|
128
|
+
rc = false
|
129
|
+
i = 0
|
130
|
+
size = messages.size
|
131
|
+
|
132
|
+
# loop through all messages but the last
|
133
|
+
while size > 1 && i < size - 1 do
|
134
|
+
rc = send_message messages.at(i), true
|
135
|
+
i += 1
|
136
|
+
end
|
137
|
+
|
138
|
+
# send the last message without the multipart arg to flush
|
139
|
+
# the message to the 0mq queue
|
140
|
+
rc = send_messages.last if size > 0
|
141
|
+
rc
|
142
|
+
end
|
143
|
+
|
144
|
+
# Retrieve the IDENTITY value assigned to this socket.
|
145
|
+
#
|
146
|
+
def identity() @raw_socket.identity; end
|
147
|
+
|
148
|
+
# Assign a custom IDENTITY value to this socket. Limit is
|
149
|
+
# 255 bytes and must be greater than 0 bytes.
|
150
|
+
#
|
151
|
+
def identity=(value) @raw_socket.identity = value; end
|
152
|
+
|
153
|
+
# Used by the reactor. Never called by user code.
|
154
|
+
#
|
155
|
+
# FIXME: need to rework all of this +rc+ stuff. The underlying lib returns
|
156
|
+
# nil when a NOBLOCK socket gets EAGAIN. It returns true when a message
|
157
|
+
# was successfully dequeued. The use of rc here is really ugly and wrong.
|
158
|
+
#
|
159
|
+
def resume_read
|
160
|
+
messages = []
|
161
|
+
rc = read_message_part messages
|
162
|
+
|
163
|
+
while 0 == rc && @raw_socket.more_parts?
|
164
|
+
rc = read_message_part messages
|
165
|
+
end
|
166
|
+
|
167
|
+
# only deliver the messages when rc is 0; otherwise, we
|
168
|
+
# may have gotten EAGAIN and no message was read;
|
169
|
+
# don't deliver empty messages
|
170
|
+
deliver messages, rc unless 0 == rc
|
171
|
+
end
|
172
|
+
|
173
|
+
# Used by the reactor. Never called by user code.
|
174
|
+
#
|
175
|
+
def resume_write
|
176
|
+
@state = :ready
|
177
|
+
@handler.on_writable self
|
178
|
+
end
|
179
|
+
|
180
|
+
def inspect
|
181
|
+
"kind [#{@kind}] poll options [#{@poll_options}] state [#{@state}]"
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def ready_state?
|
188
|
+
:ready == @state
|
189
|
+
end
|
190
|
+
|
191
|
+
def read_message_part messages
|
192
|
+
message = ZMQ::Message.new
|
193
|
+
begin
|
194
|
+
rc = @raw_socket.recv message, ZMQ::NOBLOCK
|
195
|
+
rc = 0 if rc
|
196
|
+
rescue ZMQ::ZeroMQError => e
|
197
|
+
rc = e
|
198
|
+
end
|
199
|
+
messages << message
|
200
|
+
rc
|
201
|
+
end
|
202
|
+
|
203
|
+
def deliver messages, rc
|
204
|
+
if 0 == rc
|
205
|
+
@state = :ready
|
206
|
+
@handler.on_readable self, messages
|
207
|
+
else
|
208
|
+
@handler.on_readable_error self, rc
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end # module Base
|
213
|
+
|
214
|
+
end # module Socket
|
215
|
+
|
216
|
+
end # module ZMQMachine
|