zmqmachine 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.
- 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
|